GoFのデザインパターンとは?23種類の一覧と代表パターンをコードで解説
デザインパターンは、オブジェクト指向設計で繰り返し現れる問題に対する「定石」をまとめた設計の型です。なかでも中心にあるのが、1994年に4人の技術者(GoF)が体系化した23種類のパターンです。この記事では、23種類を生成・構造・振る舞いの3分類で一覧にし、Singletonなど代表的な5つをJavaのコードで示したうえで、あえて使うべきでない場面まで踏み込みます。名前だけ覚えて終わらせず、どの設計課題にどのパターンが効くのかを判断できる状態を目指します。
まとめ:デザインパターンは「設計の共通語彙」
デザインパターンは、良い設計を毎回ゼロから考える代わりに、先人が検証した解決策を名前付きで再利用する仕組みです。GoFの23種類は生成(5種)・構造(7種)・振る舞い(11種)の3つに分類されます。生成はオブジェクトの作り方、構造はクラスやオブジェクトの組み立て方、振る舞いはオブジェクト間の責任分担を扱います。
重要なのは、パターンを暗記することではなく「この設計の変更しづらさは、どのパターンで解けるか」を結びつけられることです。一方で、単純な処理に無理やりパターンを当てはめると、かえってコードが複雑になります。まずは下の一覧で全体像をつかみ、代表パターンのコードで手ざわりを確かめてください。
デザインパターンの定義とGoFの位置づけ
再利用可能な設計の「型」という定義
デザインパターンとは、ソフトウェア設計で繰り返し発生する課題に対して、実績のある解決の構造を名前付きでまとめたものです。アルゴリズムやライブラリのように「そのまま貼り付けて動くコード」ではなく、クラスやインターフェースの関係性の型を指します。同じ「Observer」という名前で通じるため、設計者どうしが意図を短い言葉で共有できます。
パターンは魔法ではありません。適用すると多くの場合クラス数やインターフェースが増え、その分の間接コストを払います。見返りとして得られるのは、将来の変更に対する強さ(拡張のしやすさ)です。つまりデザインパターンは、オープンクローズドの原則のような設計原則を、具体的なクラス構成に落とし込んだ実装例だと捉えると理解しやすくなります。
GoFと書籍『デザインパターン』(1994年)
GoFは「Gang of Four(四人組)」の略で、Erich Gamma、Richard Helm、Ralph Johnson、John Vlissidesの4人を指します。彼らが1994年に著した『Design Patterns: Elements of Reusable Object-Oriented Software』(邦題『オブジェクト指向における再利用のためのデザインパターン』)が、23種類のパターンをカタログとして整理し、以後の標準になりました。「GoFのデザインパターン」と呼ぶときは、この23種類を指します。
着想のもとは、建築家クリストファー・アレグザンダーが著書『A Pattern Language』(1977年)で提唱した「パターンランゲージ」です。建物や街の設計で繰り返される良い解決策を語彙化する考え方を、GoFがソフトウェア設計へ持ち込みました。この経緯を知っておくと、パターンが「厳密な仕様」ではなく「設計を語るための共通語」だという性格が腑に落ちます。
GoFデザインパターン23種類の一覧【3分類】
23種類を分類・目的とともに一覧にしました。まず全体を俯瞰し、気になったパターン名から個別の解説やコードへ進むと効率的です。分類の内訳は生成5種・構造7種・振る舞い11種です。
| パターン名 | 分類 | 主な目的 |
|---|---|---|
| Singleton | 生成 | インスタンスを1つに限定する |
| Factory Method | 生成 | 生成処理をサブクラスに委譲する |
| Abstract Factory | 生成 | 関連するオブジェクト群を一括生成する |
| Builder | 生成 | 複雑なオブジェクトの生成を段階化する |
| Prototype | 生成 | 既存インスタンスの複製で生成する |
| Adapter | 構造 | 互換性のないインターフェースを変換する |
| Bridge | 構造 | 抽象と実装を分離して独立に拡張する |
| Composite | 構造 | 階層構造を単一要素と同じ形で扱う |
| Decorator | 構造 | 機能を動的に付け足す |
| Facade | 構造 | 複雑なサブシステムに窓口を用意する |
| Flyweight | 構造 | 共有でインスタンス数を抑える |
| Proxy | 構造 | 代理を挟んでアクセスを制御する |
| Chain of Responsibility | 振る舞い | 処理を担当者の連鎖で受け渡す |
| Command | 振る舞い | 操作をオブジェクトとして扱う |
| Interpreter | 振る舞い | 独自文法を解釈する |
| Iterator | 振る舞い | 内部構造を隠して順に走査する |
| Mediator | 振る舞い | オブジェクト間の相互作用を仲介する |
| Memento | 振る舞い | 状態を保存して後で復元する |
| Observer | 振る舞い | 状態変化を関係先へ通知する |
| State | 振る舞い | 状態に応じてふるまいを切り替える |
| Strategy | 振る舞い | アルゴリズムを差し替え可能にする |
| Template Method | 振る舞い | 処理の骨格を定義し一部を差し替える |
| Visitor | 振る舞い | 処理をデータ構造から分離する |
すべてを暗記する必要はありません。実務で登場頻度が高いのはSingleton・Factory Method・Adapter・Strategy・Observer・Facadeあたりです。この記事では、このうち登場頻度がとくに高いSingletonからObserverまでの5つをコード付きで解説します。
3つの分類とそれぞれの役割
生成に関するパターン(5種)
オブジェクトを「どう作るか」を扱う分類です。new演算子を直接書くと、生成するクラスが呼び出し側に固定され、差し替えが難しくなります。生成パターンは、この「作る責任」を専用の場所へ切り出し、何を作るかを柔軟に変えられるようにします。Singleton・Factory Method・Abstract Factory・Builder・Prototypeの5種が含まれます。
構造に関するパターン(7種)
クラスやオブジェクトを「どう組み合わせて大きな構造を作るか」を扱う分類です。既存クラスをそのまま使いたいが形が合わない、機能を後から足したい、複雑な内部を隠したい、といった要求に応えます。Adapter・Bridge・Composite・Decorator・Facade・Flyweight・Proxyの7種で、継承よりも委譲(オブジェクトの持ち合い)を軸にする点が特徴です。委譲と継承の使い分けは継承の使いどころと委譲との使い分けで整理しています。
振る舞いに関するパターン(11種)
オブジェクト間の「責任分担とやり取り」を扱う、最も種類の多い分類です。処理の手順を差し替える、状態変化を通知する、状態に応じてふるまいを変える、といった動的な関係を整理します。Strategy・Observer・Command・State・Template Method・Iteratorなど11種があり、条件分岐が肥大化しがちな箇所をクラスの構成に置き換えるのが狙いです。
代表的なデザインパターンをコードで理解する
ここからは登場頻度の高い5つを、Javaの最小コードで示します。いずれも「何を可変にしたいか」に注目すると、パターンの狙いが見えてきます。
Singleton(シングルトン)
設定情報やログ出力口のように、アプリ全体で1つだけあれば十分な対象を、複数生成させないためのパターンです。コンストラクタをprivateにして外部からのnewを封じ、唯一のインスタンスをクラス側で管理します。
public class AppConfig {
// 唯一のインスタンスをクラス内に保持する
private static final AppConfig INSTANCE = new AppConfig();
// コンストラクタを private にして外部からの new を封じる
private AppConfig() {}
public static AppConfig getInstance() {
return INSTANCE;
}
}
手軽ですが乱用は禁物です。どこからでも参照できる分、実質的なグローバル変数になり、状態を持つSingletonはテストで差し替えづらくなります。設定の読み取り専用など、状態を持たない用途に絞るのが安全です。
Factory Method(ファクトリーメソッド)
生成するクラスを呼び出し側に固定せず、「何を作るか」の判断をサブクラスへ委ねるパターンです。メール・SMS・プッシュ通知のように、同じ手順で扱いたいが実体は増えていく対象に向きます。
interface Notification {
void send(String message);
}
class EmailNotification implements Notification {
public void send(String message) { /* メール送信 */ }
}
// 生成の判断をサブクラスに任せる
abstract class NotificationService {
abstract Notification createNotification();
void notifyUser(String message) {
Notification n = createNotification();
n.send(message);
}
}
class EmailService extends NotificationService {
Notification createNotification() {
return new EmailNotification();
}
}
呼び出し側はNotificationServiceの手順だけを知り、具体的な通知クラスを直接newしません。新しい通知手段が増えても、サービス側のコードは変更せずサブクラスを追加するだけで済みます。
Adapter(アダプター)
すでにあるクラスを使いたいのに、呼び出し側が期待するインターフェースと形が合わない、というときに橋渡しするパターンです。外部ライブラリや古いコードを、書き換えずに新しい設計へ組み込めます。
// 既存の、変更できない摂氏クラス
class CelsiusSensor {
double getCelsius() { return 25.0; }
}
// 呼び出し側が期待するインターフェース
interface FahrenheitSensor {
double getFahrenheit();
}
// 摂氏を華氏に変換して橋渡しするアダプター
class SensorAdapter implements FahrenheitSensor {
private final CelsiusSensor sensor;
SensorAdapter(CelsiusSensor sensor) {
this.sensor = sensor;
}
public double getFahrenheit() {
return sensor.getCelsius() * 9 / 5 + 32;
}
}
摂氏を返す既存クラスを、華氏を期待する側へそのまま渡すために変換をはさんでいます。元のクラスにも呼び出し側にも手を入れずに接続できる点が、Adapterの利点です。
Strategy(ストラテジー)
アルゴリズムや処理方法を、実行時に差し替え可能にするパターンです。決済方法やソート方法のように「やることは同じで、やり方が複数ある」場面で、条件分岐の代わりに使います。
interface PaymentStrategy {
void pay(int amount);
}
class CreditCardPayment implements PaymentStrategy {
public void pay(int amount) { /* カード決済 */ }
}
class QrCodePayment implements PaymentStrategy {
public void pay(int amount) { /* QRコード決済 */ }
}
// 決済方法を実行時に差し替えられる
class Checkout {
private PaymentStrategy strategy;
Checkout(PaymentStrategy strategy) {
this.strategy = strategy;
}
void confirm(int amount) {
strategy.pay(amount);
}
}
支払い方法が増えてもCheckout側は変更せず、新しいPaymentStrategyの実装を渡すだけで対応できます。ifやswitchで分岐を積み増すコードを、クラスの差し替えに置き換えているわけです。
Observer(オブザーバー)
あるオブジェクトの状態変化を、関係する複数のオブジェクトへ自動で通知するパターンです。株価の更新をUIとログへ同時に伝える、といった「1対多の通知」に向きます。
import java.util.ArrayList;
import java.util.List;
interface Observer {
void update(String stock);
}
class Investor implements Observer {
public void update(String stock) {
System.out.println("値動きを受信: " + stock);
}
}
// 状態が変わったら登録済みの相手全員へ通知する
class StockMarket {
private final List<Observer> observers = new ArrayList<>();
void subscribe(Observer o) { observers.add(o); }
void priceChanged(String stock) {
for (Observer o : observers) {
o.update(stock);
}
}
}
通知する側(StockMarket)は、誰が登録しているかを個別に知らずに一括通知できます。購読者の追加・削除が疎結合になるため、GUIやイベント処理で広く使われています。
デザインパターンを学ぶメリット
最大の利点は、設計に関する「共通言語」が手に入ることです。「ここはStrategyで」と言えば、クラス構成と意図がひと言で伝わり、レビューや引き継ぎの説明コストが下がります。個々の名前は、チームの設計議論を短くする語彙として働きます。
もう1つは、変更に強い構造を最初から選びやすくなることです。パターンは「拡張しやすさ」を目的に磨かれてきたため、どこが将来変わりそうかを見極めれば、コードの重複や修正の波及を抑える設計を先回りで選べます。設計品質を個人の経験差に依存させず、チームとして一定水準に保ちやすくなります。
デザインパターンを使うべきでない場面
パターンは万能ではなく、当てはめる場所を誤ると逆効果です。設計を良くするどころか、読みにくく変更しにくいコードを生みます。次のような場面では、パターンの導入をいったん止めて考え直すべきです。
- 要件が単純で、素直に書けば数行で済む処理に、インターフェースと実装クラスを何枚も重ねている
- 「将来使うかもしれない」だけの理由で拡張点を先取りしている(YAGNI:今必要ないものは作らない、に反する)
- パターン名を使うこと自体が目的化し、実際の変更容易性が上がっていない
特に生成系や構造系のパターンは、クラス数を増やす代わりに柔軟性を買う取引です。変更が実際に見込めない箇所で払うと、間接コストだけが残ります。判断基準はシンプルで、「このパターンを外したらどんな変更が困るか」を具体的に言えないなら、まだ導入は早いということです。パターンは、痛みが見えてから当てるほうが失敗しません。前提となるオブジェクト指向の考え方はカプセル化の基本概念もあわせて確認しておくと、パターンの意図を取り違えにくくなります。
よくある質問
デザインパターンとは何ですか?
オブジェクト指向設計で繰り返し現れる問題に対する、実績のある解決の型を名前付きでまとめたものです。そのまま動くコードではなく、クラスやインターフェースの関係の設計テンプレートを指します。
GoFとは何の略ですか?
Gang of Four(ギャング・オブ・フォー=四人組)の略で、Erich Gamma、Richard Helm、Ralph Johnson、John Vlissidesの4人を指します。彼らが1994年の著書で23種類のデザインパターンを体系化しました。
デザインパターンは全部で何種類ありますか?
GoFのデザインパターンは23種類です。内訳は生成に関するもの5種類、構造に関するもの7種類、振る舞いに関するもの11種類です。これ以外にもマルチスレッド向けなど後年に整理されたパターン群はありますが、「GoF」と言えばこの23種類を指します。
デザインパターンとパターンランゲージの違いは何ですか?
パターンランゲージは建築家クリストファー・アレグザンダーが提唱した、良い設計を語彙化する一般的な考え方です。デザインパターンは、その考え方をソフトウェア設計へ応用し、GoFが23種類の具体的なパターンとしてまとめたものにあたります。
デザインパターンは今でも学ぶ価値がありますか?
あります。個々のパターンを暗記する価値は薄れていますが、「変更に強い設計をどう組むか」「その意図をどう短く共有するか」という考え方は、言語やフレームワークが変わっても通用します。名前より、どの課題にどの構造が効くかの対応づけを身につけることが重要です。