デザインパターン

目次

概要

デザインパターンは、ソフトウェア設計で繰り返し現れる問題に対する、名前付きの解決方針です。コードの書き方そのものというより、責務の分け方、依存の向き、変更しやすい境界の作り方を共有するための語彙として役立ちます。

要点

デザインパターンは、暗記して当てはめるものではなく、設計上の力のかかり方を説明するための言葉です。パターン名よりも、「何を変化から守りたいのか」「どの依存を切りたいのか」を見ることが重要です。

Pythonはサンプルに向いています。クラス、関数、プロトコル、デコレータ、クロージャを短く書けるため、パターンの意図と「現代的にはもっと簡単に書ける場合がある」という両方を示しやすいからです。

flowchart LR P["設計上の困りごと"] --> C["生成<br>どう作るか"] P --> S["構造<br>どう組み合わせるか"] P --> B["振る舞い<br>どう協調するか"] C --> CF["Factory / Builder / Prototype"] S --> SA["Adapter / Facade / Decorator"] B --> BO["Strategy / Observer / Command / State"]

デザインパターンとは

デザインパターンは、特定の実装コードではなく、よくある設計問題に対する再利用可能な構造です。

たとえば次のような問題に名前を与えます。

  • オブジェクト生成の複雑さを隠したい
  • 実装クラスを差し替えたい
  • 既存クラスのインターフェースを変換したい
  • 状態変化を複数の対象へ通知したい
  • 処理の手順は固定し、一部だけ差し替えたい
  • 操作をあとで実行・取り消し・記録できる形にしたい

名前があると、設計の会話が短くなります。「ここはObserverに近い」「Factoryで生成責務を分けたい」のように、意図を共有しやすくなります。

なぜパターンとして学ぶのか

パターンを学ぶ価値は、実装テンプレートを覚えることではありません。設計上の典型的なトレードオフを知ることにあります。

パターンを知ると、次を考えやすくなります。

  • 変更されやすい部分はどこか
  • 依存が集中している部分はどこか
  • テストしにくい理由は何か
  • 条件分岐が増え続けている理由は何か
  • 継承より委譲の方がよい場面はどこか
  • どの抽象が、将来の変更を受け止める境界になるか

デザインパターンは、コードを難しくするための装飾ではありません。むしろ、複雑さを置く場所を明確にするための整理法です。

GoFの3分類

GoFの古典的な23パターンは、意図によって3つに分類されます。

分類 扱う問題 代表例
生成 オブジェクトをどう作るか Factory Method, Abstract Factory, Builder, Prototype, Singleton
構造 オブジェクトやクラスをどう組み合わせるか Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy
振る舞い オブジェクト同士がどう協調するか Strategy, Observer, Command, State, Template Method, Iterator

この分類は、パターン名を覚えるためではなく、「いま困っているのは生成なのか、構造なのか、振る舞いなのか」を切り分けるために使うと効果的です。

パターン全体の地図

GoFパターンは数が多いため、最初から23個を横並びに覚えようとするとつらくなります。まずは「変更されやすい場所をどこへ逃がすか」で見ると整理できます。

flowchart TB Change["変更されやすいもの"] --> Create["生成方法が変わる"] Change --> Shape["接続する形が変わる"] Change --> Flow["振る舞いや手順が変わる"] Create --> FM["Factory Method<br>具体クラスを隠す"] Create --> AF["Abstract Factory<br>製品群を差し替える"] Create --> BD["Builder<br>組み立て手順を分ける"] Shape --> AD["Adapter<br>形を合わせる"] Shape --> FA["Facade<br>入口を単純にする"] Shape --> DC["Decorator<br>機能を重ねる"] Shape --> CP["Composite<br>木構造を同じように扱う"] Flow --> ST["Strategy<br>アルゴリズムを差し替える"] Flow --> OB["Observer<br>変化を通知する"] Flow --> CM["Command<br>操作を値にする"] Flow --> State["State<br>状態ごとに振る舞いを分ける"]

実務で特に出会いやすいのは、AdapterFacadeDecoratorStrategyObserverCommandStateです。これらは、外部API、middleware、イベント、認証、課金、画面状態、ジョブキューなど、日常的な設計問題に直結します。

GoFパターンの実用度の重み

この図は、この章のTierを分類ごとに合計したものです。振る舞いパターンの比重が大きいのは、アプリケーションコードでは「何をするか」より「いつ、誰が、どの順序で、どの条件で協調するか」が複雑になりやすいためです。

設計圧力から選ぶ

パターンは名前から選ぶより、コードにかかっている圧力から選びます。

設計圧力 兆候 候補
生成が散らばる if type == ... があちこちにある Factory Method, Abstract Factory
初期化が長い constructorの引数が増え続ける Builder, dataclass, 設定オブジェクト
外部APIが合わない 呼び出し側が変換処理だらけ Adapter
サブシステムが複雑 利用側が内部手順を知りすぎる Facade
横断処理を重ねたい ログ、認可、計測、キャッシュが混ざる Decorator, Proxy, middleware
条件分岐が増える 同じ分岐が複数箇所に出る Strategy, State
変化を複数箇所へ伝えたい 更新後に通知先が増え続ける Observer, Pub/Sub
操作を遅延・記録したい undo、retry、queueが必要 Command

判断フローとしては次のように考えます。

flowchart TD A["変更したいものは何か"] --> B{"生成方法か"} B -->|yes| C["Factory / Builder"] B -->|no| D{"外部との形が合わないか"} D -->|yes| E["Adapter / Facade"] D -->|no| F{"振る舞いを差し替えたいか"} F -->|yes| G["Strategy / State"] F -->|no| H{"通知・イベントか"} H -->|yes| I["Observer"] H -->|no| J{"操作を値として扱うか"} J -->|yes| K["Command"] J -->|no| L["まず単純な関数・クラスで保つ"]

最後の「まず単純な関数・クラスで保つ」は重要です。パターンは複雑さを消すものではなく、複雑さを置く場所を決めるものです。まだ複雑さが存在しないなら、導入しない判断も設計です。

生成に関するパターン

生成パターンは、オブジェクトをどう作るかを扱います。生成処理が複雑なときや、具体クラスへの依存を弱めたいときに使われます。

パターン Tier 目的 注意点
Factory Method ★★★★☆ 生成する具体クラスを呼び出し側から隠す 単純な分岐だけなら関数で十分なこともある
Abstract Factory ★★★☆☆ 関連するオブジェクト群をまとめて生成する ファミリー単位の差し替えがないと重い
Builder ★★★☆☆ 複雑な生成手順を段階的に組み立てる Pythonではdataclassやkeyword引数で足りることも多い
Singleton ★★★☆☆ インスタンスを一つに制限する グローバル状態になりやすくテストを難しくする
Prototype ★★☆☆☆ 既存オブジェクトを複製して生成する 共有参照とdeep copyに注意する

Singletonは便利に見えますが、グローバル状態を増やし、テストを難しくすることがあります。現代の設計では、DIコンテナや明示的な依存注入で代替できる場面も多いです。

構造に関するパターン

構造パターンは、クラスやオブジェクトをどう組み合わせるかを扱います。

パターン Tier 目的 典型例
Adapter ★★★★★ 既存のインターフェースを期待する形へ変換する 外部API、レガシーコードの接続
Decorator ★★★★★ 既存オブジェクトへ振る舞いを重ねる ログ、キャッシュ、認可、計測
Facade ★★★★★ 複雑なサブシステムに単純な入口を作る SDK、アプリケーションサービス
Composite ★★★★☆ 木構造を単一オブジェクトのように扱う ファイルツリー、UIコンポーネント
Proxy ★★★★☆ 代理オブジェクトでアクセス制御や遅延処理を行う lazy load、リモート呼び出し、権限確認
Bridge ★★☆☆☆ 抽象と実装を分け、それぞれ独立に変化させる UIと描画エンジンの分離
Flyweight ★★☆☆☆ 大量の細かいオブジェクトで共有可能な状態を分ける 文字、タイル、ゲームオブジェクト

AdapterFacadeは混同されやすいですが、焦点が違います。Adapterは「形を合わせる」ための変換で、Facadeは「複雑さを隠す」ための入口です。

振る舞いに関するパターン

振る舞いパターンは、オブジェクト同士の協調や処理の流れを扱います。

パターン Tier 目的 典型例
Iterator ★★★★★ 集合の内部構造を隠して順に取り出す コレクション、generator
Observer ★★★★★ 状態変化を購読者へ通知する イベント、通知、Pub/Sub
Strategy ★★★★★ アルゴリズムを差し替え可能にする ソート、課金、認証方式
Chain of Responsibility ★★★★☆ 複数の処理候補へ順番に渡す middleware、バリデーション
Command ★★★★☆ 操作をオブジェクトとして表す undo、ジョブキュー、監査ログ
State ★★★★☆ 状態ごとの振る舞いを分離する ワークフロー、注文状態
Template Method ★★★☆☆ 処理の骨格を固定し、一部を差し替える フレームワークのhook
Mediator ★★☆☆☆ 多数のオブジェクトの直接依存を集約する UIイベント、チャットルーム
Memento ★★☆☆☆ 内部状態を隠したまま保存・復元する undo、スナップショット
Visitor ★★☆☆☆ データ構造と処理を分離する ASTの解析、変換
Interpreter ★☆☆☆☆ 文法や式を評価する 小さなDSL

条件分岐が増え続けるコードでは、StrategyStateが有効なことがあります。ただし、分岐が少なく安定しているなら、単純な ifmatch の方が読みやすい場合もあります。

よく使うパターンの構造

実務で頻出するパターンは、構造を図で覚えると使いどころを判断しやすくなります。

Adapterは、呼び出し側が期待する形と既存APIの形を合わせます。

classDiagram class Client class Target { request() } <<interface>> Target class Adapter { request() } class LegacyApi { specific_request() } Client --> Target Adapter ..|> Target Adapter --> LegacyApi

Decoratorは、同じインターフェースを保ったまま機能を重ねます。

classDiagram class Component { operation() } <<interface>> Component class ConcreteComponent class Decorator { component operation() } class LoggingDecorator class CacheDecorator ConcreteComponent ..|> Component Decorator ..|> Component Decorator --> Component LoggingDecorator --|> Decorator CacheDecorator --|> Decorator

Observerは、状態変化を複数の購読者へ伝えます。

sequenceDiagram participant Subject participant ObserverA participant ObserverB ObserverA->>Subject: subscribe ObserverB->>Subject: subscribe Subject->>Subject: state changes Subject-->>ObserverA: notify(event) Subject-->>ObserverB: notify(event)

StrategyStateはどちらも分岐を外へ出しますが、意図が違います。Strategyは「アルゴリズムを選ぶ」、Stateは「状態遷移に応じて振る舞いが変わる」です。

flowchart LR Context["Context"] --> Strategy["Strategy<br>差し替え可能なアルゴリズム"] Context --> State["State<br>現在状態に応じた振る舞い"] Strategy --> S1["DiscountRule"] Strategy --> S2["ShippingRule"] State --> T1["Draft"] State --> T2["Submitted"] State --> T3["Paid"]

Commandは、操作を値として扱うためのパターンです。キュー、retry、undo、監査ログと相性がよいです。

flowchart LR UI["UI / API"] --> C["Command<br>execute()"] C --> Q["Queue / History"] Q --> H["Handler"] H --> D["Domain object"]

このような図は、実装の細部を固定するものではありません。むしろ、「どの依存を直接つながないようにしているか」を見るための補助線です。

リファクタリング例

パターンは、最初から導入するより、コードに痛みが出てから小さく導入する方がうまくいくことが多いです。たとえば、課金方式の分岐が増えてきたケースを考えます。

def price(plan: str, base: int) -> int:
    if plan == "regular":
        return base
    if plan == "student":
        return int(base * 0.8)
    if plan == "campaign":
        return int(base * 0.7)
    raise ValueError(plan)

この段階では、まだ if で十分です。しかし、同じ plan 分岐が請求、表示、メール文面、監査ログに広がると、変更のたびに複数箇所を直すことになります。

flowchart TD A["plan分岐"] --> B["price()"] A --> C["invoice()"] A --> D["email()"] A --> E["audit_log()"]

この圧力が見えてきたら、StrategyとFactoryを組み合わせて、分岐を一箇所に寄せます。

from collections.abc import Callable

PriceRule = Callable[[int], int]

def regular(base: int) -> int:
    return base

def student(base: int) -> int:
    return int(base * 0.8)

def campaign(base: int) -> int:
    return int(base * 0.7)

def price_rule(plan: str) -> PriceRule:
    rules = {
        "regular": regular,
        "student": student,
        "campaign": campaign,
    }
    return rules[plan]

def price(plan: str, base: int) -> int:
    return price_rule(plan)(base)

図にすると、呼び出し側は price_rule だけを知り、個々の計算方式には直接依存しません。

flowchart LR Client["呼び出し側"] --> Factory["price_rule(plan)"] Factory --> R["regular"] Factory --> S["student"] Factory --> C["campaign"] Client --> Use["選ばれたruleを実行"]

ここで重要なのは、「Strategyパターンを使った」ことではなく、分岐の増殖を止めたことです。もし分岐が1箇所にしかないなら、最初の if のままでよい場合もあります。パターンは、複雑さが実際に増えている場所へ局所的に使います。

Pythonで見るGoF 23パターン

以下のサンプルは、パターンの構造を理解するための最小例です。実務では、Pythonの関数、Protocoldataclass、標準ライブラリを使うことで、古典的なクラス構造より短く表現できることがあります。

Factory Method

Factory Methodは、呼び出し側を具体クラスから切り離します。生成方法を一箇所に集めることで、差し替えやテストがしやすくなります。

from dataclasses import dataclass
from typing import Protocol

class Notifier(Protocol):
    def send(self, message: str) -> None:
        ...

@dataclass
class EmailNotifier:
    address: str

    def send(self, message: str) -> None:
        print(f"email to {self.address}: {message}")

@dataclass
class SlackNotifier:
    channel: str

    def send(self, message: str) -> None:
        print(f"slack {self.channel}: {message}")

def create_notifier(kind: str) -> Notifier:
    if kind == "email":
        return EmailNotifier("ops@example.com")
    if kind == "slack":
        return SlackNotifier("#alerts")
    raise ValueError(f"unknown notifier: {kind}")

notifier = create_notifier("slack")
notifier.send("deploy completed")

使われ方: 通知方式、ストレージ、認証プロバイダ、外部APIクライアントなどを設定値で切り替える場面でよく使います。呼び出し側は Notifier だけを見ればよく、具体クラスの生成条件を知らなくて済みます。

見極め: 分岐が1箇所だけで増えないなら単純な関数で十分です。生成後のオブジェクトを複数箇所で同じ規則で作るようになったら、Factoryとして切り出す価値が出ます。

Abstract Factory

Abstract Factoryは、関連するオブジェクト群をまとめて生成します。UIのテーマ、DBドライバ、クラウドプロバイダ差し替えのように、部品の組み合わせを一貫させたいときに使います。

from typing import Protocol

class Button(Protocol):
    def render(self) -> str:
        ...

class Checkbox(Protocol):
    def render(self) -> str:
        ...

class LightButton:
    def render(self) -> str:
        return "light button"

class LightCheckbox:
    def render(self) -> str:
        return "light checkbox"

class DarkButton:
    def render(self) -> str:
        return "dark button"

class DarkCheckbox:
    def render(self) -> str:
        return "dark checkbox"

class UIFactory(Protocol):
    def create_button(self) -> Button:
        ...

    def create_checkbox(self) -> Checkbox:
        ...

class LightFactory:
    def create_button(self) -> Button:
        return LightButton()

    def create_checkbox(self) -> Checkbox:
        return LightCheckbox()

class DarkFactory:
    def create_button(self) -> Button:
        return DarkButton()

    def create_checkbox(self) -> Checkbox:
        return DarkCheckbox()

def render_settings(factory: UIFactory) -> None:
    print(factory.create_button().render())
    print(factory.create_checkbox().render())

render_settings(DarkFactory())

使われ方: UIテーマ、クラウド環境、DB方言、テスト用/本番用の依存セットなど、関連する部品をまとめて差し替える場面で使います。DarkFactory はdark用のbuttonとcheckboxを同じファミリーとして作ります。

見極め: 単一オブジェクトの生成だけならFactory Methodで足ります。複数の部品の組み合わせ整合性を守りたいときにAbstract Factoryが効きます。

Builder

Builderは、複雑な生成手順を段階的に組み立てます。Pythonではkeyword引数や dataclass で十分なことも多いですが、手順や検証をまとめたいときに有効です。

from dataclasses import dataclass, field

@dataclass
class Query:
    table: str
    columns: list[str] = field(default_factory=list)
    where: str | None = None

class QueryBuilder:
    def __init__(self, table: str) -> None:
        self.query = Query(table)

    def select(self, *columns: str) -> "QueryBuilder":
        self.query.columns.extend(columns)
        return self

    def filter(self, condition: str) -> "QueryBuilder":
        self.query.where = condition
        return self

    def build(self) -> str:
        columns = ", ".join(self.query.columns) or "*"
        sql = f"SELECT {columns} FROM {self.query.table}"
        if self.query.where:
            sql += f" WHERE {self.query.where}"
        return sql

sql = QueryBuilder("users").select("id", "name").filter("active = true").build()
print(sql)

使われ方: SQLHTTP request、設定オブジェクト、複雑な検索条件など、段階的に組み立てる値に向きます。メソッドチェーンにすると、生成手順を読みやすくできます。

見極め: Pythonではkeyword引数や dataclass で十分な場合が多いです。手順の順序、検証、デフォルト、派生値が増えてきたときにBuilderを検討します。

Singleton

Singletonは、インスタンスを一つに制限します。ただしグローバル状態になりやすいため、Pythonでは明示的に依存として渡す方が扱いやすいことも多いです。

class Settings:
    _instance: "Settings | None" = None

    def __new__(cls) -> "Settings":
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.debug = False
        return cls._instance

first = Settings()
second = Settings()
second.debug = True

print(first is second)
print(first.debug)

使われ方: 設定、ロガー、メトリクス送信器など「プロセス内で1つだけ」としたい対象で見かけます。例では Settings() を何度呼んでも同じインスタンスになります。

見極め: テストしにくいグローバル状態になりやすいため、乱用は避けます。多くの場合、アプリケーション起動時に1回作って依存として渡す方が安全です。

Prototype

Prototypeは、既存オブジェクトを複製して新しいオブジェクトを作ります。設定テンプレートやゲームオブジェクトの初期値複製などに向きます。

from copy import deepcopy
from dataclasses import dataclass, field

@dataclass
class ServerConfig:
    image: str
    env: dict[str, str] = field(default_factory=dict)

base = ServerConfig("app:latest", {"LOG_LEVEL": "INFO"})
staging = deepcopy(base)
staging.env["LOG_LEVEL"] = "DEBUG"

print(base)
print(staging)

使われ方: 設定テンプレート、フォームの初期状態、ゲームオブジェクト、ワークフロー定義など、既存の雛形から少し変えたものを作る場面で使います。

見極め: shallow copyとdeep copyの違いに注意します。辞書やリストのようなmutableな値を共有してしまうと、片方の変更が別のオブジェクトへ漏れます。

Adapter

Adapterは、既存クラスのインターフェースを、利用側が期待する形へ変換します。

from typing import Protocol

class LegacyPaymentGateway:
    def pay_in_yen(self, yen: int) -> bool:
        print(f"legacy payment: {yen} JPY")
        return True

class PaymentPort(Protocol):
    def charge(self, amount: int) -> bool:
        ...

class PaymentAdapter:
    def __init__(self, gateway: LegacyPaymentGateway) -> None:
        self.gateway = gateway

    def charge(self, amount: int) -> bool:
        return self.gateway.pay_in_yen(amount)

def purchase(payment: PaymentPort) -> None:
    if payment.charge(3000):
        print("purchase completed")

purchase(PaymentAdapter(LegacyPaymentGateway()))

使われ方: 外部SDK、古いクラス、別チームのAPI、テストダブルなどを、自分たちのアプリケーションが期待する PaymentPort に合わせる場面で使います。

見極め: Adapterは境界に置くのが基本です。ドメインロジックの中に外部APIの都合を持ち込まず、入口で変換してから内部モデルへ渡すと保守しやすくなります。

Decorator

Decoratorは、既存の処理に追加の振る舞いを重ねます。Pythonでは言語機能としてのdecoratorがあるため、パターンの意図を短く表現できます。

from collections.abc import Callable
from functools import wraps
from time import perf_counter

def measure(func: Callable[..., str]) -> Callable[..., str]:
    @wraps(func)
    def wrapper(*args: object, **kwargs: object) -> str:
        start = perf_counter()
        result = func(*args, **kwargs)
        elapsed = perf_counter() - start
        print(f"{func.__name__}: {elapsed:.4f}s")
        return result

    return wrapper

@measure
def render_report() -> str:
    return "report"

print(render_report())

使われ方: ログ、計測、キャッシュ、認可、リトライ、トランザクション境界など、主処理の前後に横断的な処理を重ねる場面で使います。Pythonのdecorator構文はこの用途に特に自然です。

見極め: 重ねすぎると実行順序が追いづらくなります。副作用のあるdecoratorは名前と責務を明確にし、テストで挙動を固定します。

Facade

Facadeは、複雑なサブシステムに単純な入口を作ります。呼び出し側が細かい手順を知らなくてよいようにします。

class AuthService:
    def verify(self, token: str) -> bool:
        return token == "valid"

class ProfileService:
    def load_name(self) -> str:
        return "Ada"

class UserFacade:
    def __init__(self) -> None:
        self.auth = AuthService()
        self.profile = ProfileService()

    def current_user_name(self, token: str) -> str | None:
        if not self.auth.verify(token):
            return None
        return self.profile.load_name()

print(UserFacade().current_user_name("valid"))

使われ方: 複数サービス呼び出し、SDK、外部連携、アプリケーションサービス層など、利用側に細かい手順を見せたくない場面で使います。例では認証とプロフィール取得を UserFacade にまとめています。

見極め: Facadeは複雑さを消すのではなく隠します。内部が肥大化しすぎると巨大な神クラスになるため、Facade自体の責務も分割できるように保ちます。

Composite

Compositeは、木構造の葉と枝を同じインターフェースで扱います。ファイルツリーやUIコンポーネントでよく使われます。

from dataclasses import dataclass, field
from typing import Protocol

class Node(Protocol):
    def size(self) -> int:
        ...

@dataclass
class File:
    name: str
    bytes: int

    def size(self) -> int:
        return self.bytes

@dataclass
class Directory:
    name: str
    children: list[Node] = field(default_factory=list)

    def size(self) -> int:
        return sum(child.size() for child in self.children)

root = Directory("root", [File("a.txt", 100), Directory("logs", [File("app.log", 900)])])
print(root.size())

使われ方: ファイルシステム、メニュー、UIコンポーネント、組織階層、カテゴリツリーなど、葉と枝を同じ操作で扱いたい場面で使います。FileDirectorysize() を持つため再帰的に集計できます。

見極め: 木構造でないものに無理に適用すると分かりにくくなります。親子関係、再帰処理、同一インターフェースの3つが揃うと有効です。

Proxy

Proxyは、対象オブジェクトへのアクセスを代理します。遅延ロード、権限確認、キャッシュ、リモート呼び出しなどに使えます。

class Report:
    def render(self) -> str:
        return "expensive report"

class LazyReportProxy:
    def __init__(self) -> None:
        self._report: Report | None = None

    def render(self) -> str:
        if self._report is None:
            print("loading report")
            self._report = Report()
        return self._report.render()

proxy = LazyReportProxy()
print(proxy.render())
print(proxy.render())

使われ方: lazy load、アクセス制御、キャッシュ、レート制限、リモート呼び出しの代理などで使います。例では重い Report を必要になるまで作りません。

見極め: Proxyは本体と同じように見えるため、裏で通信やI/Oが走ると利用者が驚くことがあります。遅延や失敗の可能性があるProxyはドキュメント化します。

Bridge

Bridgeは、抽象と実装を分け、それぞれを独立に変更できるようにします。UIと描画先、通知内容と送信経路などの分離に使えます。

from typing import Protocol

class Renderer(Protocol):
    def draw_text(self, text: str) -> None:
        ...

class HtmlRenderer:
    def draw_text(self, text: str) -> None:
        print(f"<p>{text}</p>")

class ConsoleRenderer:
    def draw_text(self, text: str) -> None:
        print(text)

class Page:
    def __init__(self, renderer: Renderer) -> None:
        self.renderer = renderer

    def show_title(self, title: str) -> None:
        self.renderer.draw_text(title.upper())

Page(HtmlRenderer()).show_title("design patterns")
Page(ConsoleRenderer()).show_title("design patterns")

使われ方: 抽象側と実装側が別々に増える場合に使います。例では Page の表示ロジックと Renderer の出力先を分け、HTML/Consoleを独立に差し替えています。

見極め: 単なる依存注入と似ています。抽象の種類と実装の種類がどちらも増える見込みがあるとき、継承階層の爆発を避けるためにBridgeが役立ちます。

Flyweight

Flyweightは、大量のオブジェクトで共有できる状態を分離します。文字、タイル、アイコンなどを大量に扱う場面に向きます。

from dataclasses import dataclass

@dataclass(frozen=True)
class Glyph:
    char: str
    font: str

class GlyphFactory:
    def __init__(self) -> None:
        self._cache: dict[tuple[str, str], Glyph] = {}

    def get(self, char: str, font: str) -> Glyph:
        key = (char, font)
        if key not in self._cache:
            self._cache[key] = Glyph(char, font)
        return self._cache[key]

factory = GlyphFactory()
a = factory.get("A", "Noto Sans")
b = factory.get("A", "Noto Sans")
print(a is b)

使われ方: 文字、アイコン、地図タイル、ゲームの粒子など、同じ内部状態を持つ小さなオブジェクトが大量に出る場面で使います。共有できる状態をcacheし、個別状態は外側で持ちます。

見極め: メモリ削減が主目的なので、対象数が少ない場合は不要です。共有する状態をimmutableに保つと、意図しない変更を避けやすくなります。

Iterator

Iteratorは、集合の内部構造を隠して順に値を取り出します。Pythonではiterator protocolとgeneratorが言語に組み込まれています。

from collections.abc import Iterator

class Countdown:
    def __init__(self, start: int) -> None:
        self.start = start

    def __iter__(self) -> Iterator[int]:
        current = self.start
        while current > 0:
            yield current
            current -= 1

for value in Countdown(3):
    print(value)

使われ方: コレクション、ファイル読み込み、ページングAPI、ストリーム処理などで使います。Pythonでは __iter__yield により、Iteratorは日常的な構文になっています。

見極め: 全件をリスト化せず、必要な分だけ順に処理したいときに有効です。I/OやDB cursorと組み合わせる場合は、リソース解放のタイミングに注意します。

Observer

Observerは、ある対象の状態変化を複数の購読者へ通知します。

from collections.abc import Callable

Subscriber = Callable[[str], None]

class EventBus:
    def __init__(self) -> None:
        self._subscribers: list[Subscriber] = []

    def subscribe(self, subscriber: Subscriber) -> None:
        self._subscribers.append(subscriber)

    def publish(self, event: str) -> None:
        for subscriber in self._subscribers:
            subscriber(event)

def write_log(event: str) -> None:
    print(f"log: {event}")

def send_metric(event: str) -> None:
    print(f"metric: {event}")

bus = EventBus()
bus.subscribe(write_log)
bus.subscribe(send_metric)
bus.publish("user_registered")

使われ方: イベント通知、UI更新、ドメインイベント、監視、Pub/Subなどで使います。発行側は購読者を具体的に知らず、イベントだけを通知します。

見極め: 便利ですが、処理の流れが見えにくくなりがちです。イベント名、payload、購読解除、失敗時の扱いを設計しておくと事故が減ります。

Strategy

Strategyは、アルゴリズムを差し替え可能にするパターンです。Pythonでは、クラスではなく関数を渡すだけで自然に表現できます。

from collections.abc import Callable

Discount = Callable[[int], int]

def no_discount(price: int) -> int:
    return price

def seasonal_discount(price: int) -> int:
    return int(price * 0.9)

def vip_discount(price: int) -> int:
    return int(price * 0.8)

def checkout(price: int, discount: Discount) -> int:
    return discount(price)

print(checkout(10_000, seasonal_discount))
print(checkout(10_000, vip_discount))

使われ方: 課金計算、割引、検索、ソート、認証方式、圧縮方式など、同じ入口でアルゴリズムだけを差し替える場面で使います。Pythonでは関数を渡すだけで軽く表現できます。

見極め: 条件分岐が増え続けるときに有効です。一方、分岐が2個程度で増えないなら、単純な if の方が読みやすいこともあります。

Chain of Responsibility

Chain of Responsibilityは、処理を複数の候補へ順番に渡します。Web middlewareやバリデーションの連鎖でよく使われます。

from collections.abc import Callable

Handler = Callable[[dict[str, str]], str | None]

def require_user(request: dict[str, str]) -> str | None:
    return None if request.get("user") else "401 Unauthorized"

def require_admin(request: dict[str, str]) -> str | None:
    return None if request.get("role") == "admin" else "403 Forbidden"

def handle(request: dict[str, str], handlers: list[Handler]) -> str:
    for handler in handlers:
        result = handler(request)
        if result is not None:
            return result
    return "200 OK"

print(handle({"user": "ada", "role": "admin"}, [require_user, require_admin]))

使われ方: Web middleware、入力バリデーション、権限チェック、ログ処理、問い合わせの段階的処理などで使います。各handlerは「処理する」か「次へ渡す」かを決めます。

見極め: 順序依存が強いので、handlerの並びを設定として明確にします。途中で止まる条件と、最後まで通った場合の挙動をテストすると安全です。

Command

Commandは、操作をオブジェクトとして表します。実行、取り消し、キューイング、ログ保存に向いています。

from dataclasses import dataclass
from typing import Protocol

class Command(Protocol):
    def execute(self) -> None:
        ...

    def undo(self) -> None:
        ...

@dataclass
class RenameFile:
    old_name: str
    new_name: str

    def execute(self) -> None:
        print(f"rename {self.old_name} -> {self.new_name}")

    def undo(self) -> None:
        print(f"rename {self.new_name} -> {self.old_name}")

history: list[Command] = []

command = RenameFile("draft.md", "article.md")
command.execute()
history.append(command)

last = history.pop()
last.undo()

使われ方: undo/redo、ジョブキュー、監査ログ、バッチ実行、GUI操作などで使います。操作をオブジェクトにすることで、あとから実行・保存・取り消しできます。

見極め: 単純な関数呼び出しで十分な場面では重いです。操作を履歴として扱う、再実行する、取り消す、キューに積む必要が出たらCommandが自然です。

State

Stateは、状態ごとの振る舞いをクラスや関数に分離します。状態遷移が増えて if が肥大化したときに有効です。

from typing import Protocol

class OrderState(Protocol):
    def pay(self, order: "Order") -> None:
        ...

    def ship(self, order: "Order") -> None:
        ...

class Pending:
    def pay(self, order: "Order") -> None:
        print("paid")
        order.state = Paid()

    def ship(self, order: "Order") -> None:
        print("cannot ship before payment")

class Paid:
    def pay(self, order: "Order") -> None:
        print("already paid")

    def ship(self, order: "Order") -> None:
        print("shipped")
        order.state = Shipped()

class Shipped:
    def pay(self, order: "Order") -> None:
        print("already shipped")

    def ship(self, order: "Order") -> None:
        print("already shipped")

class Order:
    def __init__(self) -> None:
        self.state: OrderState = Pending()

    def pay(self) -> None:
        self.state.pay(self)

    def ship(self) -> None:
        self.state.ship(self)

order = Order()
order.ship()
order.pay()
order.ship()

使われ方: 注文、チケット、ワークフロー、接続状態、ゲームキャラクターなど、状態によって許可される操作が変わる場面で使います。状態ごとの処理をクラスに分離できます。

見極め: 状態数が少ないうちは match やテーブル駆動で十分です。状態ごとの振る舞いが増え、遷移ルールが複雑になったらStateを検討します。

Template Method

Template Methodは、処理の骨格を基底側に置き、一部だけサブクラスで差し替えます。継承を使うため、委譲で十分かも確認します。

from abc import ABC, abstractmethod

class DataImporter(ABC):
    def run(self, path: str) -> None:
        raw = self.read(path)
        rows = self.parse(raw)
        self.save(rows)

    def read(self, path: str) -> str:
        return "name,score\nAda,100"

    @abstractmethod
    def parse(self, raw: str) -> list[dict[str, str]]:
        ...

    def save(self, rows: list[dict[str, str]]) -> None:
        print(rows)

class CsvImporter(DataImporter):
    def parse(self, raw: str) -> list[dict[str, str]]:
        header, line = raw.splitlines()
        keys = header.split(",")
        values = line.split(",")
        return [dict(zip(keys, values))]

CsvImporter().run("scores.csv")

使われ方: フレームワーク、インポート処理、テストfixture、バッチ処理など、全体手順は固定で一部だけ差し替える場面で使います。例では読み込み、解析、保存の骨格を基底クラスに置いています。

見極め: 継承に依存するため、階層が深くなると追いづらくなります。Pythonでは高階関数や委譲で同じ意図を軽く表現できる場合もあります。

Mediator

Mediatorは、多数のオブジェクトが互いに直接参照し合うのを避け、やり取りを仲介役へ集めます。

class ChatRoom:
    def __init__(self) -> None:
        self.users: list[User] = []

    def join(self, user: "User") -> None:
        self.users.append(user)
        user.room = self

    def send(self, sender: "User", message: str) -> None:
        for user in self.users:
            if user is not sender:
                user.receive(sender.name, message)

class User:
    def __init__(self, name: str) -> None:
        self.name = name
        self.room: ChatRoom | None = None

    def send(self, message: str) -> None:
        if self.room:
            self.room.send(self, message)

    def receive(self, sender: str, message: str) -> None:
        print(f"{self.name} <- {sender}: {message}")

room = ChatRoom()
ada = User("Ada")
linus = User("Linus")
room.join(ada)
room.join(linus)
ada.send("hello")

使われ方: UIコンポーネント間の連携、チャットルーム、複数サービスの調整役などで使います。オブジェクト同士が直接参照し合う網目状の依存を避けられます。

見極め: Mediatorにロジックが集まりすぎると新しい神オブジェクトになります。単なる連絡係か、ビジネスルールを持つ調整役かを分けて考えます。

Memento

Mementoは、オブジェクトの内部状態を隠したまま保存・復元します。undoやスナップショットで使われます。

from dataclasses import dataclass

@dataclass(frozen=True)
class EditorSnapshot:
    text: str

class Editor:
    def __init__(self) -> None:
        self.text = ""

    def type(self, value: str) -> None:
        self.text += value

    def save(self) -> EditorSnapshot:
        return EditorSnapshot(self.text)

    def restore(self, snapshot: EditorSnapshot) -> None:
        self.text = snapshot.text

editor = Editor()
editor.type("hello")
snapshot = editor.save()
editor.type(" world")
editor.restore(snapshot)
print(editor.text)

使われ方: undo、エディタの履歴、フォームの一時保存、ゲームのセーブポイントなどで使います。内部状態を外から直接触らず、snapshotとして保存します。

見極め: snapshotが大きい場合はメモリコストに注意します。差分保存、永続化、履歴数制限などと組み合わせることが多いです。

Visitor

Visitorは、データ構造と処理を分離します。ASTの評価、整形、型検査のように、同じ構造に複数の処理を追加したい場合に使われます。

from dataclasses import dataclass
from typing import Protocol

class Visitor(Protocol):
    def visit_number(self, node: "Number") -> int:
        ...

    def visit_add(self, node: "Add") -> int:
        ...

class Expr(Protocol):
    def accept(self, visitor: Visitor) -> int:
        ...

@dataclass
class Number:
    value: int

    def accept(self, visitor: Visitor) -> int:
        return visitor.visit_number(self)

@dataclass
class Add:
    left: Expr
    right: Expr

    def accept(self, visitor: Visitor) -> int:
        return visitor.visit_add(self)

class Evaluator:
    def visit_number(self, node: Number) -> int:
        return node.value

    def visit_add(self, node: Add) -> int:
        return node.left.accept(self) + node.right.accept(self)

expr = Add(Number(2), Add(Number(3), Number(4)))
print(expr.accept(Evaluator()))

使われ方: ASTの評価、整形、型検査、コード生成など、同じデータ構造に複数の処理を追加したい場面で使います。データ構造と処理を分離できます。

見極め: 新しい処理を追加しやすい反面、新しいノード型を追加するとvisitor側の実装が増えます。データ構造が安定し、処理種類が増える場合に向きます。

Interpreter

Interpreterは、小さな文法や式を評価します。大きな言語処理系ではなく、設定式や検索条件のような小さなDSLに向きます。

from dataclasses import dataclass
from typing import Protocol

class Expr(Protocol):
    def eval(self, env: dict[str, int]) -> int:
        ...

@dataclass
class Number:
    value: int

    def eval(self, env: dict[str, int]) -> int:
        return self.value

@dataclass
class Var:
    name: str

    def eval(self, env: dict[str, int]) -> int:
        return env[self.name]

@dataclass
class Add:
    left: Expr
    right: Expr

    def eval(self, env: dict[str, int]) -> int:
        return self.left.eval(env) + self.right.eval(env)

expr = Add(Var("x"), Number(3))
print(expr.eval({"x": 7}))

使われ方: 小さなDSL、フィルタ式、ルールエンジン、検索条件、計算式の評価などで使います。例では式木を作り、環境 env に基づいて評価しています。

見極め: 文法が大きくなるならparser generatorや既存ライブラリを使う方が安全です。小さく閉じた式言語に限定すると扱いやすいです。

使いどころと注意点

デザインパターンは、使えば設計がよくなる魔法ではありません。問題がない場所に先回りして導入すると、抽象が増えすぎて読みにくくなります。

導入を考える目安は次の通りです。

  • 同じ種類の分岐や生成処理が何度も出ている
  • 変更のたびに関係ない場所まで修正している
  • テストで差し替えたい依存が直接埋め込まれている
  • 責務が大きくなり、クラスや関数の説明が難しい
  • 将来増えるバリエーションがかなり明確である

逆に、まだ要件が見えていない段階で過度に抽象化すると、将来の変更方向と合わない設計になりやすいです。

現代的な見方

GoFのデザインパターンはオブジェクト指向を前提にしたものが多いですが、現代の言語では別の機能で自然に表現できることがあります。

古典的なパターン 現代的な代替・補助
Strategy 関数、クロージャ、インターフェース
Observer イベント、Pub/Sub、リアクティブストリーム
Factory DIコンテナ、モジュール境界
Template Method 高階関数、委譲、コンポジション
Iterator 言語組み込みのiterator / generator
Decorator 言語機能のdecorator、middleware
Command job queue、event sourcing、undo stack

パターン名を使うよりも、まず言語機能とチームの慣習で自然に表現できるかを考えると、過剰設計を避けやすくなります。

パターンを選ぶための観点

パターンを選ぶときは、名前から探すより、設計上の圧力から考える方が実践的です。

困りごと 候補
生成処理が複雑 Factory Method, Builder
実装をまとめて差し替えたい Abstract Factory, Strategy
外部APIの形が合わない Adapter
複雑な内部構造を隠したい Facade
共通処理を重ねたい Decorator, Proxy
状態ごとの分岐が増えた State
イベントを複数箇所へ伝えたい Observer
操作を記録・取り消ししたい Command, Memento
木構造を同じインターフェースで扱いたい Composite

導入前には、「この抽象によって削れる複雑さ」と「この抽象が新しく生む複雑さ」の両方を見る必要があります。

Pythonで軽く書くための道具

PythonでGoFパターンを使うときは、JavaやC++ のクラス構造をそのまま移植しない方が自然なことがあります。言語機能を使うと、パターンの意図だけを軽く表現できます。

道具 使いどころ
Protocol 継承ではなく「必要なメソッドを持つか」でStrategyAdapterを表す
dataclass BuilderやValue Objectの定義を簡潔にする
first-class function StrategyCommandTemplate Methodを関数として渡す
generator Iteratorをクラスなしで表す
decorator syntax Decoratorパターンや横断的処理を読みやすく重ねる
context manager acquire / releaseを持つ処理を安全に囲む

たとえばStrategyは、必ずしもクラスでなくても表現できます。

from collections.abc import Callable

PriceRule = Callable[[int], int]

def regular(price: int) -> int:
    return price

def campaign(price: int) -> int:
    return int(price * 0.9)

def checkout(price: int, rule: PriceRule) -> int:
    return rule(price)

print(checkout(1000, campaign))

一方で、状態を持つ戦略、複数メソッドの契約、テスト用の差し替えが必要な場合は、クラスや Protocol の方が読みやすくなります。パターンは「形」ではなく「変更点をどこに閉じ込めるか」を考えるための語彙として使います。

Refactoring.guru デザインパターン実装

Refactoring.guru (refactoring.guru) は、デザインパターンをビジュアルと実装例で解説します。

Singleton パターン

class Database:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._initialized = False
        return cls._instance
    
    def __init__(self):
        if self._initialized:
            return
        self.connection = self._connect()
        self._initialized = True

db1 = Database()
db2 = Database()
assert db1 is db2

Factory Method パターン

from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
    @abstractmethod
    def create_payment(self):
        pass

class CreditCardProcessor(PaymentProcessor):
    def create_payment(self):
        return CreditCardPayment()

class Payment(ABC):
    @abstractmethod
    def process_payment(self, amount):
        pass

class CreditCardPayment(Payment):
    def process_payment(self, amount):
        print(f"Credit card: ${amount}")

Builder パターン

class SQLQueryBuilder:
    def __init__(self):
        self._select = []
        self._from = None
        self._where = []
    
    def select(self, *columns):
        self._select = columns
        return self
    
    def from_table(self, table):
        self._from = table
        return self
    
    def where(self, condition):
        self._where.append(condition)
        return self
    
    def build(self):
        query = f"SELECT {', '.join(self._select)} FROM {self._from}"
        if self._where:
            query += " WHERE " + " AND ".join(self._where)
        return query

query = (SQLQueryBuilder()
    .select('id', 'name')
    .from_table('users')
    .where('age > 18')
    .build())

Adapter パターン

class LegacyPaymentSystem:
    def do_payment(self, amount):
        return f"Legacy: {amount}"

class ModernPaymentInterface:
    def pay(self, amount):
        raise NotImplementedError

class LegacyPaymentAdapter(ModernPaymentInterface):
    def __init__(self, legacy_system):
        self.legacy_system = legacy_system
    
    def pay(self, amount):
        return self.legacy_system.do_payment(amount)

Decorator パターン

from functools import wraps

def log_execution(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

def retry(max_attempts=3):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except:
                    if attempt == max_attempts - 1:
                        raise
        return wrapper
    return decorator

@retry(max_attempts=3)
@log_execution
def send_email(recipient, message):
    print(f"To {recipient}: {message}")

Facade パターン

class UserAuthService:
    def authenticate(self, username, password):
        return username == "admin"

class RoleService:
    def get_user_roles(self, username):
        return ["admin"]

class AuditService:
    def log_login(self, username):
        print(f"Logged in: {username}")

class AuthFacade:
    def __init__(self):
        self.auth = UserAuthService()
        self.roles = RoleService()
        self.audit = AuditService()
    
    def login(self, username, password):
        if self.auth.authenticate(username, password):
            roles = self.roles.get_user_roles(username)
            self.audit.log_login(username)
            return {"success": True, "roles": roles}
        return {"success": False}

auth = AuthFacade()
auth.login("admin", "secret")

Observer パターン

from abc import ABC, abstractmethod

class Observer(ABC):
    @abstractmethod
    def update(self, subject):
        pass

class DataStore:
    def __init__(self):
        self._data = None
        self._observers = []
    
    def attach(self, observer):
        self._observers.append(observer)
    
    def notify(self):
        for observer in self._observers:
            observer.update(self)
    
    @property
    def data(self):
        return self._data
    
    @data.setter
    def data(self, value):
        self._data = value
        self.notify()

class LogObserver(Observer):
    def update(self, subject):
        print(f"Data: {subject.data}")

store = DataStore()
store.attach(LogObserver())
store.data = 50

Strategy パターン

from abc import ABC, abstractmethod

class SortingStrategy(ABC):
    @abstractmethod
    def sort(self, data):
        pass

class BubbleSort(SortingStrategy):
    def sort(self, data):
        n = len(data)
        for i in range(n):
            for j in range(0, n-i-1):
                if data[j] > data[j+1]:
                    data[j], data[j+1] = data[j+1], data[j]
        return data

class QuickSort(SortingStrategy):
    def sort(self, data):
        if len(data) <= 1:
            return data
        pivot = data[len(data) // 2]
        return (self.sort([x for x in data if x < pivot]) +
                [x for x in data if x == pivot] +
                self.sort([x for x in data if x > pivot]))

class DataProcessor:
    def __init__(self, strategy):
        self.strategy = strategy
    
    def process(self, data):
        return self.strategy.sort(data)

processor = DataProcessor(QuickSort())
processor.process([64, 34, 25, 12])

Command パターン

from abc import ABC, abstractmethod

class Command(ABC):
    @abstractmethod
    def execute(self):
        pass
    
    @abstractmethod
    def undo(self):
        pass

class OpenFileCommand(Command):
    def __init__(self, filename):
        self.filename = filename
    
    def execute(self):
        return f"Opening {self.filename}"
    
    def undo(self):
        return f"Closing {self.filename}"

class SaveFileCommand(Command):
    def __init__(self, filename, content):
        self.filename = filename
        self.content = content
    
    def execute(self):
        return f"Saving {self.filename}"
    
    def undo(self):
        return f"Unsaving {self.filename}"

class TextEditor:
    def __init__(self):
        self.history = []
    
    def execute_command(self, command):
        command.execute()
        self.history.append(command)
    
    def undo_last(self):
        if self.history:
            command = self.history.pop()
            command.undo()

editor = TextEditor()
editor.execute_command(OpenFileCommand("doc.txt"))
editor.undo_last()

State パターン

from abc import ABC, abstractmethod

class OrderState(ABC):
    @abstractmethod
    def handle(self, order):
        pass

class PendingState(OrderState):
    def handle(self, order):
        print("Pending")
        order.state = PaymentState()

class PaymentState(OrderState):
    def handle(self, order):
        print("Payment")
        order.state = ShippingState()

class ShippingState(OrderState):
    def handle(self, order):
        print("Shipping")

class Order:
    def __init__(self):
        self.state = PendingState()
    
    def process(self):
        self.state.handle(self)

order = Order()
order.process()
order.process()

SourceMaking パターン実装

SourceMaking (sourcemaking.com) では、古典的デザインパターンの詳細な実装例を提供しています。

パターン選択の判断基準

パターン 目的 複雑度
Singleton グローバル管理
Factory オブジェクト生成
Builder 複雑な構築
Adapter インターフェース変換
Decorator 機能拡張
Facade 複雑さ隠蔽
Observer イベント駆動
Strategy アルゴリズム切り替え
Command 命令キューイング
State 状態遷移

パターン適用のアンチパターン

  • シンプルな問題に複雑なパターンを当てはめない
  • 問題解決が目的であり、パターン使用は手段
  • フレームワーク依存のパターン実装を避ける

まとめ

デザインパターンは、設計の典型問題に名前を与えるための道具です。重要なのは、パターンを当てはめることではなく、変更される部分を見つけ、依存を適切な向きに整え、読みやすく保守しやすい境界を作ることです。

Pythonのような表現力の高い言語では、古典的なクラス図どおりに書く必要はありません。関数、Protocol、dataclass、decorator、標準ライブラリを使いながら、パターンの意図だけを軽く取り入れるのが現実的です。

参考文献

公式・標準

書籍

解説・補助