ソフトウェア開発において、オブジェクト指向ソフトウェアで繰り返し現れる設計上の問題と、その解決策を整理・体系化したものがデザインパターンです。中でも最も有名で基本となるのが、4人の著者(Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides)がまとめた「GoFデザインパターン」です。
本記事では、GoFデザインパターンの全23種類を一覧にまとめ、それぞれの目的や特徴、さらに代表的なパターンのPythonPython [パイソン]汎用プログラミング言語実装例を解説します。
GoFデザインパターンとは?
GoF(Gang of Four)デザインパターンは、1994年に出版された書籍『Design Patterns: Elements of Reusable Object-Oriented Software』で紹介された23種類の設計パターンのことです。
これらのパターンは、オブジェクト指向プログラミングにおける再利用性や拡張性などを考慮した設計の「定石」として、現代のソフトウェア開発にも大きな影響を与えています。また、さまざまなフレームワークやライブラリの設計を理解するうえでも役立ちます。
GoFデザインパターンは、その目的に応じて以下の3つのカテゴリに分類されます。
- 生成(Creational)パターン(5種類):オブジェクトの生成に関するパターン
- 構造(Structural)パターン(7種類):クラスやオブジェクトの組み合わせに関するパターン
- 振る舞い(Behavioral)パターン(11種類):オブジェクト間の責任や通信に関するパターン
それでは、各カテゴリごとにパターンの一覧を見ていきましょう。
1. 生成(Creational)パターンの一覧
オブジェクトの生成プロセスを抽象化し、システムの柔軟性を高めるためのパターンです。
| パターン名 | 目的・特徴 |
|---|---|
| Singleton(シングルトン) | クラスのインスタンスが1つだけになることを保証し、 そのインスタンスへのグローバルなアクセス手段を提供する。 |
| Factory Method(ファクトリーメソッド) | インスタンスの生成をサブクラスに委譲し、 生成するクラスを柔軟に変更できるようにする。 |
| Abstract Factory(抽象ファクトリー) | 関連する一連のオブジェクト群を、 その具象クラスを指定せずに生成するインターフェースを提供する。 |
| Builder(ビルダー) | 複雑なオブジェクトの生成過程を分離し、 同じ生成過程で異なる表現のオブジェクトを構築できるようにする。 |
| Prototypeprototype [プロトタイプ]動作確認や検証のために作成される試作品(プロトタイプ) | 典型的なインスタンス(プロトタイプ)をコピーすることで、 新しいオブジェクトを生成する。 |
【実装例】Singleton パターン(Python)
class Singleton: _instance = None
def __new__(cls): if cls._instance is None: # まだインスタンスが存在しない場合のみ生成 cls._instance = super().__new__(cls) return cls._instance
# 確認obj1 = Singleton()obj2 = Singleton()
print(obj1 is obj2) # True(同じインスタンスを参照している)※ 補足: 上記のSingleton実装はパターンの概念を説明するための簡易的なものであり、マルチスレッド環境ではスレッドセーフではありません。実際のマルチスレッド環境で利用する場合は、ロック(排他制御)などの仕組みを併用する必要があります。
2. 構造(Structural)パターンの一覧
クラスやオブジェクトを組み合わせて、より大きな構造を作るためのパターンです。
| パターン名 | 目的・特徴 |
|---|---|
| Adapter(アダプター) | 互換性のないインターフェースを変換し、 既存のクラスを修正せずに利用できるようにする。 |
| Bridge(ブリッジ) | 「機能の階層」と「実装の階層」を分離し、 それぞれを独立して拡張できるようにする。 |
| Composite(コンポジット) | 部分と全体を同じインターフェースで扱えるようにし、 木構造を表現する。 |
| Decorator(デコレーター) | オブジェクトに動的に新しい責任(機能)を追加する。 サブクラス化の代替手段。 |
| Facade(ファサード) | 複数の複雑なサブシステムに対する シンプルな窓口(インターフェース)を提供する。 |
| Flyweight(フライウェイト) | 多数の細かいオブジェクトを効率的に共有し、 メモリ使用量を削減する。 |
| Proxyproxy [プロキシ]クライアントとサーバーの間に立って通信を中継するサーバー(プロキシ) | オブジェクトへのアクセスを制御するための代理(プロキシ)オブジェクトを提供する。 |
【実装例】Adapter パターン(Python)
# 既存のクラス(インターフェースが合わない)class OldSystem: def old_method(self): return "古いシステムでの処理"
# アダプタークラスclass Adapter: def __init__(self, old_system): self.old_system = old_system
def new_method(self): # 古いメソッドを新しいインターフェースに合わせて呼び出す return self.old_system.old_method()
# 利用側old_sys = OldSystem()adapter = Adapter(old_sys)print(adapter.new_method()) # "古いシステムでの処理"※ 補足: GoFの原著において、Adapterパターンには多重継承を用いる「クラスアダプタ」と、上記のように委譲(インスタンスの保持)を用いる「オブジェクトアダプタ」の2種類が言及されています。このコード例はオブジェクトアダプタの実装例となります。
3. 振る舞い(Behavioral)パターンの一覧
オブジェクト間の責任の割り当てや、アルゴリズムの制御に関するパターンです。
| パターン名 | 目的・特徴 |
|---|---|
| Observer(オブザーバー) | 状態の変化を監視し、変化があった際に依存するオブジェクトに自動的に通知する。 |
| Strategy(ストラテジー) | アルゴリズムをカプセル化し、実行時に動的に切り替えられるようにする。 |
| State(ステート) | オブジェクトの内部状態が変化したときに、その振る舞いを変更できるようにする。 |
| Command(コマンド) | 要求(操作)をオブジェクトとしてカプセル化し、取り消し(Undo)やキューイングを可能にする。 |
| Chain of Responsibility(責任連鎖) | 複数のオブジェクトをチェーン状に繋ぎ、要求を処理できるオブジェクトが見つかるまで順に渡していく。 |
| Iterator(イテレータ) | コレクションの内部表現を公開することなく、要素に順番にアクセスする方法を提供する。 |
| Mediator(メディエーター) | オブジェクト間の複雑な通信を中央の仲介者(メディエーター)に集約し、結合度を下げる。 |
| Memento(メメント) | カプセル化を破壊せずに、オブジェクトの過去の状態を保存・復元できるようにする。 |
| Template Method(テンプレートメソッド) | 処理の骨組みをスーパークラスで定義し、具体的な処理の一部をサブクラスに委譲する。 |
| Visitor(ビジター) | データ構造を変更せずに、要素に対して新しい操作を追加できるようにする。 |
| Interpreter(インタープリタ) | 特定の言語や文法の規則を定義し、それを解釈して実行する仕組みを提供する。 |
【実装例】Strategy パターン(Python)
from abc import ABC, abstractmethod
# 戦略のインターフェースclass DiscountStrategy(ABC): @abstractmethod def calculate(self, price: int) -> int: pass
# 具象戦略1:割引なしclass NoDiscount(DiscountStrategy): def calculate(self, price: int) -> int: return price
# 具象戦略2:20%オフclass TwentyPercentOff(DiscountStrategy): def calculate(self, price: int) -> int: return int(price * 0.8)
# コンテキスト(利用側)class ShoppingCart: def __init__(self, strategy: DiscountStrategy): self.strategy = strategy
def checkout(self, price: int): final_price = self.strategy.calculate(price) print(f"最終価格は {final_price} 円です。")
# 実行時に戦略を切り替えるcart1 = ShoppingCart(NoDiscount())cart1.checkout(1000) # 最終価格は 1000 円です。
cart2 = ShoppingCart(TwentyPercentOff())cart2.checkout(1000) # 最終価格は 800 円です。💡 コラム:現代のプログラミング事情とデザインパターン
GoFデザインパターンが提唱された1994年当時から時代は進み、現代のソフトウェア開発においてはいくつかのパターンに対する考え方が変化しています。
1. Singletonは「アンチパターン」になりつつある?
前述のSingletonパターンですが、近年では 「グローバルな状態を持つためテスト容易性が低下し、コードの柔軟性を奪う」 として、アンチパターンとみなされることが増えています。 現代のWeb開発やアプリケーション開発では、自前でSingletonクラスを実装するのではなく、DI(依存性の注入)コンテナのスコープ管理(SpringやDaggerなど)を利用して、インスタンスを1つに制限するアプローチが主流となっています。
2. モダン言語(Pythonなど)によるパターンの不要化・簡略化
Pythonなどの動的型付け言語や、関数を第一級オブジェクトとして扱える(クロージャや高階関数をサポートする)モダンな言語においては、GoFのいくつかのパターンが言語機能自体に吸収されています。
- Commandパターン:わざわざクラスを定義しなくても、「高階関数」や「クロージャ(Pythonの関数オブジェクト)」を渡すだけでシンプルに代替できます。
- Iteratorパターン:Pythonのジェネレータ(
yield)や、言語標準のイテレータ構文(for ... in ...)などに完全に吸収されています。
Python等でデザインパターンを学ぶ際は、**「このパターンは言語の標準機能でよりシンプルに書けないか?」**という視点を持つと、より実用的な設計スキルが身につきます。
まとめ
本記事では、GoFデザインパターンの全23種類を一覧で紹介しました。
デザインパターンは「必ず使わなければならないもの」ではありませんが、設計の引き出しとして知っておくことで、開発者間でのコミュニケーション(共通言語)や保守性の高いコード設計に大きく役立ちます。
まずは「Singleton」「Factory Method」「Observer」など、代表的なパターンから学び、実際のプロジェクトに適用できそうな場面を探してみることをおすすめします。
以上で本記事の解説を終わります。
よいITライフを!
人気記事
- 1
- 2
- 3
- 4
- 5
「絶対に挫折させない」という著者の強い意志を感じる、プログラミングの「最初の1冊」としておすすめできる良書です。