はじめに
はじめまして!この春からエンジニアとして働き始めた新入社員です。
現在、入社研修の一環としてPythonを使った課題に取り組んでいます。その中で デザインパターン を学ぶ機会があり、「せっかくなら記事にしてアウトプットしよう」と思い、この記事を書きました。
研修中の方や、Pythonを学び始めた方の参考にもなれば幸いです。
プログラミングを学び始めて少し経つと、こんな悩みが出てきませんか?
「コードは動くけど、なんかごちゃごちゃしている…」
「機能を追加するたびに、あちこち修正しないといけない…」
「他の人のコードが読めない…」
そんなときに助けてくれるのが デザインパターン です。
このシリーズでは、Pythonで知っておきたい5つのデザインパターンを、具体的なコード例とともに解説します。
デザインパターンとは
デザインパターンとは、ソフトウェア開発において よく遭遇する問題に対する、再利用可能な設計の「定石」 のことです。
1994年に出版された書籍 Design Patterns: Elements of Reusable Object-Oriented Software(通称「GoFの本」)で23のパターンが体系化され、現在も広く使われています。
デザインパターンを学ぶメリットは主に3つです。
| メリット | 説明 |
|---|---|
| 可読性の向上 | パターン名で設計意図を共有できる |
| 保守性の向上 | 変更に強いコードが書ける |
| 再利用性の向上 | 似た問題に同じ解法を適用できる |
このシリーズで紹介する5つのパターンは以下のとおりです。
| パターン | 一言で言うと | 使いどころ |
|---|---|---|
| Strategy | アルゴリズムを差し替える | 処理の種類が増えそうな場合 |
| Factory | オブジェクト生成を一元管理 | 生成するクラスを切り替えたい場合 |
| Observer | 変化を自動通知 | イベント駆動・疎結合にしたい場合 |
| Template Method | 骨格は共通、中身は任せる | 同じ手順で一部だけ異なる処理がある場合 |
| Decorator | 機能を動的に追加 | 機能の組み合わせを柔軟にしたい場合 |
今回はこの5つのパターンのうちStrategyパターンについて書いていきたいと思います!
Strategy パターン(RPG 攻撃方法の切り替え)
どういったものか
「アルゴリズムをカプセル化して、動的に切り替えられる」 パターンです。
たとえばRPGゲームで、キャラクターが「剣」「魔法」「弓」など複数の攻撃方法を持つ場面を考えてみましょう。Strategyパターンを使わずに書くと、次のようになります。
class Character:
def attack(self, attack_type: str, target: str) -> str:
if attack_type == "sword":
return f"剣で {target} を斬りつけた!"
elif attack_type == "magic":
return f"魔法で {target} に炎を放った!"
elif attack_type == "bow":
return f"弓で {target} を射抜いた!"
# 攻撃方法が増えるたびに elif を追加し続けなければならない…
新しい攻撃方法(例:「投げナイフ」「サンダー魔法」)が増えるたびに、Character クラス自体を修正する必要があります。Strategyパターンを使うと、攻撃方法(戦略)をクラスとして分離し、キャラクターが持つ戦略を外から自由に差し替えられるようになります。
Character(キャラクター) ──uses──> AttackStrategy(攻撃方法)
├── SwordAttack(剣)
├── MagicAttack(魔法)
└── BowAttack(弓)
どのように書くか
問題設定: RPGゲームで、キャラクターが「剣で攻撃」「魔法で攻撃」「弓で攻撃」など複数の攻撃方法を持つ場面を考えます。攻撃処理を if/else でキャラクタークラスに直接書いていくと、攻撃方法が増えるたびにクラスを修正しなければなりません。Strategyパターンを使うと、攻撃方法を「戦略」として分離し、キャラクターが持つ戦略を自由に差し替えられるようになります。
from abc import ABC, abstractmethod
# Strategy(戦略)の基底クラス
class AttackStrategy(ABC):
@abstractmethod
def attack(self, target: str) -> str:
pass
# 具体的な戦略A:剣攻撃
class SwordAttack(AttackStrategy):
def attack(self, target: str) -> str:
return f"⚔️ 剣で {target} を斬りつけた! ダメージ: 50"
# 具体的な戦略B:魔法攻撃
class MagicAttack(AttackStrategy):
def attack(self, target: str) -> str:
return f"✨ 魔法で {target} に炎を放った! ダメージ: 80"
# 具体的な戦略C:弓攻撃
class BowAttack(AttackStrategy):
def attack(self, target: str) -> str:
return f"🏹 弓で {target} を射抜いた! ダメージ: 35"
# Context(文脈):戦略を使う側
class Character:
def __init__(self, name: str, strategy: AttackStrategy):
self.name = name
self._strategy = strategy
def change_attack(self, strategy: AttackStrategy) -> None:
"""戦闘中でも攻撃方法を切り替えられる"""
self._strategy = strategy
def attack(self, target: str) -> str:
return f"【{self.name}】 {self._strategy.attack(target)}"
# 使い方
hero = Character("アレックス", SwordAttack())
print(hero.attack("スライム"))
# → 【アレックス】 ⚔️ 剣で スライム を斬りつけた! ダメージ: 50
hero.change_attack(MagicAttack())
print(hero.attack("ドラゴン"))
# → 【アレックス】 ✨ 魔法で ドラゴン に炎を放った! ダメージ: 80
hero.change_attack(BowAttack())
print(hero.attack("ゴブリン"))
# → 【アレックス】 🏹 弓で ゴブリン を射抜いた! ダメージ: 35
新しい攻撃方法(例:「サンダー魔法」「投げナイフ」など)が増えても、AttackStrategy を継承した新しいクラスを追加するだけで対応できます。Character クラス本体を一切変更する必要がありません。
応用例
Strategyパターンは 「処理の種類が今後増えることが予想される」 場面で特に力を発揮します。
- ECサイトの割引計算 — 通常価格・会員割引・セール割引など、割引ロジックをStrategyとして分離することで、新しい割引キャンペーンが追加されても既存コードに触れずに対応できます。
- ファイルの出力形式 — CSV・JSON・Excelなど、出力形式ごとにStrategyを用意することで、形式の追加・変更が容易になります。
- 認証方式の切り替え — パスワード認証・OAuth・SSO(シングルサインオン)など、認証方法を戦略として切り替えられるようにすることで、複数の認証プロバイダに対応しやすくなります。
まとめ
今回はデザインパターンの一つであるStrategyパターンについて書きました。
Strategyパターンについてまとめると以下のようになります。
| 項目 | 内容 |
|---|---|
| 目的 | アルゴリズムをカプセル化し、実行時に切り替えられるようにする |
| 解決する問題 |
if/elif の肥大化・処理追加のたびに既存クラスを修正する問題 |
| メリット | 新しい戦略の追加が既存コードを変えずにできる |
| デメリット | クラス数が増える。戦略が1〜2種類しかないなら過剰設計になることも |
✅ こういうときに使う
- 同じ処理に「バリエーション」が複数ある
- バリエーションが今後も増える予定がある
- 実行時(ランタイム)に処理を切り替えたい
❌ 使わなくてよいとき
- 処理のバリエーションが2種類以下で、今後も増える予定がない
- 単純な
if/elseで十分に読みやすい場合
次回はFactoryパターンについて書こうと思います!