GoFのデザインパターンは、1994年に4人の著者がまとめた23種類の設計の型です。この記事では23種類を生成・構造・振る舞いの3分類で一覧にし、代表5パターンをJavaのコードで示します。そのうえで、混同しやすいパターンの見分け方、Java標準ライブラリに入っている実例、ラムダやsealedで書き方が変わったパターン、導入を見送るべき場面まで扱います。
まとめ:GoFデザインパターン23種類の要点
GoFのデザインパターンは生成5種・構造7種・振る舞い11種の計23種類です。生成はオブジェクトの作り方、構造はクラスとオブジェクトの組み合わせ方、振る舞いはオブジェクト間の責任分担を扱います。本記事で入口として取り上げるのはSingleton・Factory Method・Adapter・Facade・Strategy・Observerの6つです。目標は名前を覚えることではなく、それぞれが解く課題と、導入したときに増える負担を説明できるようになることです。
一部のパターンは、ラムダやsealedなどの言語機能で書き方が短くなりました。それでも解いている課題は残っているため、パターンは暗記するものではなく「どの変更に備えるための構造か」を判断する語彙として使います。
デザインパターンとGoFの定義
デザインパターンは名前の付いた設計の型
デザインパターンとは、ソフトウェア設計で繰り返し起きる課題に対し、実績のある解決の構造を名前付きでまとめたものです。ライブラリのようにそのまま貼り付けて動くコードではなく、クラスとインターフェースの関係の型を指します。「ここはObserverで」と言えば、通知する側と受け取る側の関係がひと言で伝わります。
代わりに払うコストもあります。パターンによってはクラスとインターフェースが増え、処理を追うときの間接層も増えます。見返りは将来の変更への強さで、オープンクローズドの原則(OCP)のような設計原則を具体的なクラス構成に落としたものがパターンだと捉えると位置づけがはっきりします。
GoFの4人と原著『Design Patterns』・日本語訳
GoFは「Gang of Four」の略で、Erich Gamma、Richard Helm、Ralph Johnson、John Vlissidesの4人を指します。4人の著書『Design Patterns: Elements of Reusable Object-Oriented Software』は1994年10月にAddison-Wesleyから刊行されました。「GoFのデザインパターン」と言えば、この本のカタログに載った23種類を指します。
日本語訳は『オブジェクト指向における再利用のためのデザインパターン』です。国立国会図書館の書誌では、1995年10月にソフトバンクから出版され(CiNii Booksによれば本位田真一・吉田和樹の監訳)、1999年11月に改訂版がソフトバンクパブリッシングから出ています。
パターンという考え方の源流は、建築家クリストファー・アレグザンダーの『A Pattern Language』(1977年、Oxford University Press)です。建物や街の設計で繰り返し現れる良い解決を語彙にまとめる方法を、GoFがオブジェクト指向設計に持ち込みました。
GoFが前提にした2つの設計原則
原著の第1章は、23パターンに共通する原則として2つを掲げています。1つは「実装ではなくインターフェースに対してプログラムせよ(Program to an interface, not an implementation)」、もう1つは「クラスの継承よりオブジェクトのコンポジションを優先せよ(Favor object composition over class inheritance)」です。
23種類の多くは、この2原則のどちらかを特定の課題に当てはめた形をしています。Strategyはアルゴリズムをインターフェースの裏に隠し、Decoratorは継承の代わりに包み込みで機能を足します。継承と委譲の境目は継承の使いどころと委譲との使い分け、インターフェース越しに実装を差し替える仕組みはポリモフィズム(多態性)で詳しく扱っています。
GoFデザインパターン23種類の一覧表
23種類を分類と解決する課題で並べました。気になる行から、下の分類別の説明と個別記事へ進んでください。
| パターン名 | 分類 | 解決する課題 |
|---|---|---|
| Singleton | 生成 | インスタンスを1つに限定する |
| Factory Method | 生成 | 作るクラスの決定をサブクラスへ任せる |
| Abstract Factory | 生成 | 関連する部品の一式をまとめて作る |
| Builder | 生成 | 複雑なオブジェクトを段階的に組み立てる |
| Prototype | 生成 | 既存インスタンスを複製して作る |
| Adapter | 構造 | 合わないインターフェースを変換する |
| Bridge | 構造 | 抽象と実装を分け、別々に拡張する |
| Composite | 構造 | 木構造の枝と葉を同じ型で扱う |
| Decorator | 構造 | 包み込んで機能を後から足す |
| Facade | 構造 | 複雑なサブシステムに窓口を1つ置く |
| Flyweight | 構造 | 共通する内部状態を共有してメモリを抑える |
| Proxy | 構造 | 代理を挟んでアクセスを制御する |
| Chain of Responsibility | 振る舞い | 処理できる相手まで要求を順に渡す |
| Command | 振る舞い | 操作をオブジェクトにして保存・取り消しする |
| Interpreter | 振る舞い | 小さな言語の文法をクラスで表す |
| Iterator | 振る舞い | 内部構造を隠して順に走査する |
| Mediator | 振る舞い | 多対多のやり取りを仲介役に集める |
| Memento | 振る舞い | 内部状態を公開せず保存し、後で復元する |
| Observer | 振る舞い | 状態の変化を複数の相手へ通知する |
| State | 振る舞い | 状態ごとに振る舞いをクラスで切り替える |
| Strategy | 振る舞い | アルゴリズムを外から差し替える |
| Template Method | 振る舞い | 処理の骨格を固定し一部を差し替える |
| Visitor | 振る舞い | データ構造を変えずに処理を追加する |
全部を同じ重さで覚える必要はありません。Interpreter・Flyweight・Mementoのように扱う課題が限られるものは、対応する課題に出会った段階で詳しく学べば十分です。Strategy(比較やソートの基準を渡す)、Observer(イベントリスナー)、Adapter(既存APIの型合わせ)、Facade(複数のAPIをまとめた窓口)は、標準ライブラリやフレームワークのAPIとして目にする機会が多いパターンです。
生成・構造・振る舞いの3分類
生成パターン(5種):生成の責任と手順の分離
生成パターンは「何をどう作るか」を呼び出し側から切り離します。呼び出し側にnew EmailNotification()と書くと、通知手段を増やすたびに呼び出し側を書き換えることになります。生成の判断を呼び出し側から分離すれば、製品クラスや生成クラスの追加は必要でも、利用側への変更は抑えられます。
Singleton・Factory Method・Abstract Factory・Builder・Prototypeの5種があり、Factory MethodとAbstract Factoryの違いはファクトリパターンのJava・Python実装例、Builderの2系統(GoF型とEffective Java型)はBuilderパターンとGoのFunctional Optionsで個別に扱っています。
構造パターン(7種):クラスとオブジェクトの組み合わせ
構造パターンは、既存のクラスやオブジェクトを組み合わせて大きな構造を作る型です。形が合わない既存クラスをつなぐ、機能を後から足す、複雑な内部を隠す、といった要求に応えます。Adapter・Bridge・Composite・Decorator・Facade・Flyweight・Proxyの7種があります。継承を使うクラス版のAdapterもありますが、多くはオブジェクトを持ち合う委譲で組み立てます。
窓口クラスの作り方とLaravelのFacadeとの名前の衝突は、Facadeパターンの実装とLaravel Facadeとの違いで整理しています。
振る舞いパターン(11種):責任分担とオブジェクト間の協調
振る舞いパターンは、アルゴリズムの受け持ち、オブジェクト間の責任分担と連携の仕方を整理する最大の分類です。StrategyやStateのように増え続けるif文・switch文を差し替え可能なオブジェクトに置き換えるもの、Observerのように変更を通知するもの、Iteratorのように走査を隠すものが含まれます。
個別記事として、Strategyパターンの実装例と使いどころ、ObserverパターンとPub/Subとの違い、テンプレートメソッドパターンとStrategyとの使い分け、Visitorパターンとダブルディスパッチがあります。
代表5パターンのJavaコード
生成・構造・振る舞いの各分類から選んだ5つを、Javaの最小コードで示します。どのコードも「何を可変にしたいか」を見ると狙いが読み取れます。すべてJDK 25でコンパイルと実行を確認しています。
Singleton:唯一のインスタンスへのアクセス
アプリ全体で1つあれば足りる設定やキャッシュを、複数作らせないためのパターンです。コンストラクタをprivateにして外部からのnewを封じ、唯一のインスタンスをクラス自身が持ちます。
public class AppConfig {
private static final AppConfig INSTANCE = new AppConfig();
private AppConfig() {}
public static AppConfig getInstance() {
return INSTANCE;
}
}
どこからでもgetInstance()で取れるため、実質的にはグローバル変数です。可変の状態を持たせるとテストで差し替えられなくなるので、読み取り専用の設定などに用途を絞ります。Javaでのより安全な書き方(enum)は後の章で示します。
Factory Method:サブクラスによる生成クラスの決定
メール・SMSのように、同じ手順で扱いたいが実体が増えていく対象に使います。親クラスは処理の手順だけを持ち、具体的に何を作るかはサブクラスが決めます。
interface Notification {
String send(String message);
}
class EmailNotification implements Notification {
public String send(String message) {
return "mail:" + message;
}
}
abstract class NotificationService {
// 何を作るかはサブクラスが決める
abstract Notification createNotification();
String notifyUser(String message) {
return createNotification().send(message);
}
}
class EmailService extends NotificationService {
Notification createNotification() {
return new EmailNotification();
}
}
SMSを足すときはSmsNotificationとSmsServiceを追加するだけで、NotificationServiceの手順は変わりません。
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;
}
}
new SensorAdapter(new CelsiusSensor()).getFahrenheit()は77.0を返します。既存クラスにも呼び出し側にも手を入れずに接続できるのがAdapterの価値です。
Strategy:外部からのアルゴリズムの差し替え
決済方法や割引計算のように、やることは同じでやり方が複数ある場面で、条件分岐の代わりに使います。
interface PaymentStrategy {
String pay(int amount);
}
class CreditCardPayment implements PaymentStrategy {
public String pay(int amount) {
return "card:" + amount;
}
}
class QrCodePayment implements PaymentStrategy {
public String pay(int amount) {
return "qr:" + amount;
}
}
class Checkout {
private final PaymentStrategy strategy;
Checkout(PaymentStrategy strategy) {
this.strategy = strategy;
}
String confirm(int amount) {
return strategy.pay(amount);
}
}
支払い方法が増えてもCheckoutは変わらず、新しい実装を渡すだけで済みます。switch (paymentType)の分岐を積み増す書き方を、オブジェクトの差し替えに置き換えた形です。
Observer:複数の受け取り手への変更通知
株価の更新を画面とログへ同時に伝えるような、1対多の通知に使います。通知する側は、誰が受け取るかを個別に知りません。
import java.util.ArrayList;
import java.util.List;
interface PriceListener {
void onPriceChanged(String symbol, int price);
}
class StockTicker {
private final List<PriceListener> listeners = new ArrayList<>();
void subscribe(PriceListener listener) {
listeners.add(listener);
}
void update(String symbol, int price) {
for (PriceListener listener : listeners) {
listener.onPriceChanged(symbol, price);
}
}
}
PriceListenerはメソッドが1つのインターフェースなので、ticker.subscribe((s, p) -> System.out.println(s + "=" + p))のようにラムダで登録できます。受け取り手を増やしてもStockTickerは変更不要で、GUIのイベント処理で広く使われてきました。この例は登録だけに対応しており、登録解除が要るならunsubscribeメソッドを追加します。
似ているパターンの見分け方
GoFのパターンは、クラス図だけを見ると同じ形になる組み合わせがいくつもあります。見分ける手がかりは構造ではなく目的です。
Adapter・Decorator・Proxy・Facade:包む目的の違い
4つとも「別のオブジェクトを包んで、呼び出しを中継する」形をしています。違うのは、外に見せるインターフェースと、包む理由です。
| パターン | 外に見せる型 | 包む理由 |
|---|---|---|
| Adapter | 中身と別の型 | 形を合わせる |
| Decorator | 中身と同じ型 | 機能を足す(重ねられる) |
| Proxy | 中身と同じ型 | アクセスを制御する |
| Facade | 新しい簡単な型 | 複数の部品をまとめて隠す |
DecoratorとProxyは型が同じなので最も紛らわしい組み合わせです。呼び出し結果に何かを足すならDecorator、呼ぶかどうか・いつ呼ぶか(遅延生成、権限確認、リモート呼び出し)を決めるならProxyと判断します。Facadeは、サブシステムを使うための窓口を簡素化することが目的である点で他の3つと異なります。
Strategy・State・Template Method:切り替えの主体と仕組み
3つとも「処理の一部を差し替える」パターンですが、誰が切り替えるかが違います。Strategyは呼び出し側が外から実装を渡します。Stateはオブジェクト自身が状態遷移に合わせて内部で実装を入れ替え、呼び出し側は切り替えを意識しません。Template Methodは切り替えを継承で行い、実行時ではなくクラスを書いた時点で決まります。
注文の「未払い→支払済→発送済」で操作の可否が変わるならState、配送料の計算方法を顧客ごとに選ばせるならStrategyです。状態の数だけで判断せず、遷移条件や禁止遷移を把握しにくくなってきたら、Stateパターンによる実装と併せてステートマシンとしての設計を検討します。
Factory Method・Abstract Factory・Builder:生成対象と構築手順の違い
Factory Methodは1種類の製品を作るメソッドをサブクラスで差し替えます。Abstract Factoryは組み合わせて使う製品の一式(WindowsのボタンとWindowsのダイアログ、のように揃っていないと困るもの)をまとめて作ります。GoFのBuilderは構築の手順と出来上がる表現を分離し、同じ手順から異なる表現(たとえば同じ文書をHTMLとPDFで)を作ります。引数の多いコンストラクタを段階的な設定に置き換える使い方は、Effective Java型のBuilderとして区別されます。
同じ型の引数が並んで取り違えやすい、省略可能な引数の組み合わせが多い、という症状ならEffective Java型のBuilder、同じ構築手順から複数の表現を作りたいならGoF型のBuilder、製品の系列(テーマ・OS・DB製品など)ごとに部品を揃える必要があるならAbstract Factory、と症状から選ぶと迷いません。
Java標準ライブラリにあるGoFパターンの実例
パターンはフレームワークや標準ライブラリの中に最初から入っています。普段使っているAPIがどのパターンかを知ると、名前と意図が結びつきやすくなります。
| パターン | Java標準ライブラリの例 |
|---|---|
| Singleton | Runtime.getRuntime() |
| Adapter | InputStreamReader |
| Decorator | BufferedInputStream |
| Proxy | java.lang.reflect.Proxy |
| Iterator | java.util.Iterator |
| Observer | java.util.Observer(非推奨) |
RuntimeのJavadocは「すべてのJavaアプリケーションはRuntimeクラスのインスタンスを1つだけ持ち、アプリケーションが自分でインスタンスを作ることはできない」と書いており、Singletonの定義そのものです。InputStreamReaderはバイトストリームを文字ストリームへつなぐ「橋渡し(bridge)」と説明されており、役割はAdapterです。名前にbridgeとあってもGoFのBridgeパターンではない点に注意してください。
BufferedInputStreamは「別の入力ストリームに機能を加える」クラスで、new BufferedInputStream(new FileInputStream(path))のように同じInputStream型を重ねて包むDecoratorの典型です。java.lang.reflect.Proxyはインターフェースの実装として振る舞いつつメソッド呼び出しを横取りできるオブジェクトを実行時に作るクラスで、呼び出しの前後にログや権限確認を差し込むProxyを、クラスを書かずに作れます。
GoFデザインパターンは古いのか:言語機能で形が変わったパターン
原著はC++とSmalltalkを前提に書かれており、その後の言語機能で不要になったり、短く書けるようになったりしたパターンがあります。「古い」と言われる理由はここにありますが、消えたのは書き方であって、解いている課題は残っています。
Norvigの指摘:Lisp・Dylanで単純化できる16種類
Peter Norvigは1996年5月5日のObject Worldでの講演「Design Patterns in Dynamic Languages」で、23種類のうち16種類は、少なくとも一部の用途ではC++よりLispやDylanのほうが見えなくなるか単純になると示しました。資料の内訳は、第一級の型で6種(Abstract Factory・Flyweight・Factory Method・State・Proxy・Chain of Responsibility)、第一級の関数で4種(Command・Strategy・Template Method・Visitor)、マクロで2種(Interpreter・Iterator)、メソッド結合で2種(Mediator・Observer)、マルチメソッドで1種(Builder)、モジュールで1種(Facade)です。
Javaもこの30年でラムダ(Java 8)、record、sealed、switchのパターンマッチングを取り込み、同じ方向に動いています。
Strategy・Visitor・Singleton:現代のJavaでの実装
Strategyは、メソッドが1つのインターフェースならクラスを作らずラムダで渡せます。Visitorは、JEP 409(Java 17)のsealedとJEP 441(Java 21)のswitchのパターンマッチングで、ダブルディスパッチを書かずに型ごとの処理を分けられます。
import java.util.List;
import java.util.function.IntUnaryOperator;
public class ModernPatterns {
// Strategy:割引計算を関数として受け取る
static int checkout(int price, IntUnaryOperator discount) {
return discount.applyAsInt(price);
}
// Visitor の代わり:sealed で型を閉じ、switch で網羅する
sealed interface Shape permits Circle, Rect {}
record Circle(double r) implements Shape {}
record Rect(double w, double h) implements Shape {}
static double area(Shape s) {
return switch (s) {
case Circle c -> Math.PI * c.r() * c.r();
case Rect r -> r.w() * r.h();
};
}
// Singleton:要素が1つの enum
enum AppConfig {
INSTANCE;
String region() {
return "ap-northeast-1";
}
}
public static void main(String[] args) {
System.out.println(checkout(1000, p -> p * 90 / 100));
for (Shape s : List.of(new Circle(1), new Rect(2, 3))) {
System.out.printf("%.2f%n", area(s));
}
System.out.println(AppConfig.INSTANCE.region());
}
}
実行結果は900、3.14、6.00、ap-northeast-1です。sealedで許可した型をswitchが網羅していないとコンパイルエラーになるため、型を追加したときの処理漏れをコンパイラが見つけてくれます。Visitorの目的は要素クラスを変えずに処理を追加することで、sealedとswitchでもareaのような処理は要素の型を触らずに足せます。加えて、型に対する分岐の実装漏れをコンパイラが検査してくれる点が、従来のVisitorにない利点です。
Singletonのenum版は、Joshua Blochの『Effective Java 第3版』Item 3の著者サンプルコードで「Enum singleton – the preferred approach」と注記されている書き方です。Java言語仕様の8.9節が、enumはクローン・リフレクションによる生成・デシリアライズのいずれでも定数以外のインスタンスを作れないと定めているためです。ただしSpringなどのDIコンテナを使うなら、Singletonを手で書かずにsingletonスコープのBeanとして依存を注入するほうがテストで差し替えやすくなります。Springのsingletonは「コンテナごと・Bean定義ごとに1つ」で、公式ドキュメントもGoFのSingletonとは異なる概念だと説明しています。依存の渡し方は依存性の注入(DI)と依存性逆転の原則(DIP)の違いを参照してください。
java.util.Observer:Java 9での非推奨化と代替API
Javaは1.0からjava.util.ObserverとObservableを標準で持っていましたが、Java 9で非推奨になりました。Javadocは理由として、イベントモデルが限定的であること、通知の順序が規定されていないこと、状態の変化と通知が1対1に対応しないことを挙げ、代わりにjava.beansパッケージ、スレッド間の順序付きメッセージにはjava.util.concurrent、リアクティブストリームにはFlow APIを勧めています。
Observerという考え方が廃れたのではなく、汎用の基底クラスで提供する作りが合わなくなったということです。Javadocが挙げる代替APIのほかに、この記事の例のような同期通知を独自に実装する場合は、上のPriceListenerのように用途専用のインターフェースを定義する方法があります。
デザインパターンを使うべきでない場面
パターンはクラス数を増やす代わりに変更への強さを買う取引です。変更が来ない場所で払えば、間接層のコストだけが残ります。次の条件に当てはまるなら、導入を止めます。
- 差し替える実装が1つしかなく、2つ目の具体的な予定もない(StrategyやFactoryを先回りで作らない)
- 独立した責務も具体的な差し替え要件もない処理に、インターフェースと実装クラスを重ねようとしている
- Singletonに可変の状態を持たせようとしている(テストの順序で結果が変わる原因になる)
- パターン名をコメントやクラス名に入れること自体が目的になっている
判断の基準は、「このパターンを外したら、どの変更が何ファイルの修正になるか」を具体的に言えるかどうかです。言えないうちは導入が早すぎます。本記事では、同じ種類の変更が2回目に来た時点でパターンへのリファクタリングを検討する、という目安を勧めます。重複したswitch文が2か所に現れたらStrategyやStateを検討する、という順番です。既存システムの設計が肥大化し、段階的な改修では要件を満たせないと判断できた場合は、移行費用と停止リスクも見積もったうえでフルスクラッチ開発による作り直しを比較します。
デザインパターンの学び方と参考書
パターンはオブジェクト指向の基礎(カプセル化・継承・ポリモーフィズム)の上に成り立っています。オブジェクト指向の三本柱とカプセル化が曖昧なまま23種類に進むと、パターンの意図を取り違えやすくなります。
順番は、上の一覧で全体像をつかむ、代表5パターンを自分の言語で書いてみる、使っているフレームワークの中から同じ形を探す、の3段階が効率的です。list.sort()に渡すComparatorをStrategyとして、SwingのaddActionListener()をObserverとして読み直すと定着します。
| 書籍 | 出版社・年 | 向く読者 |
|---|---|---|
| オブジェクト指向における再利用のためのデザインパターン 改訂版 | ソフトバンクパブリッシング 1999 | 原典を辞書として引きたい人 |
| Java言語で学ぶデザインパターン入門 第3版(結城浩) | SBクリエイティブ 2021 | Javaで23種を順に学ぶ人 |
| Head Firstデザインパターン 第2版 | オライリー・ジャパン 2022 | 図解で頻出パターンから入る人 |
結城浩の第3版は2021年11月にSBクリエイティブから刊行され、23種類すべてをJavaのサンプルで扱っています。Head Firstの日本語版第2版は2022年6月刊・672ページです。原典の日本語訳は記述が抽象的なので、入門書で手を動かしたあとに辞書として引く使い方が合っています。
よくある質問
GoFとは何の略ですか?
Gang of Four(4人組)の略です。『Design Patterns』を書いたErich Gamma、Richard Helm、Ralph Johnson、John Vlissidesの4人を指し、転じてこの本と23種類のパターンそのものもGoFと呼ばれます。
GoFのデザインパターンは全部で何種類ですか?
23種類です。内訳は生成5種類、構造7種類、振る舞い11種類です。マルチスレッド向けのパターンやエンタープライズアプリケーション向けのパターンなど後年まとめられたパターン集もありますが、GoFと言えばこの23種類を指します。
GoFのデザインパターンは古いですか?
書き方が古くなったものはあります。JavaでもStrategyは関数型インターフェースならラムダで、Visitorは型の集合をsealedで閉じられるならswitchで短く書けます。一方で、解いている課題(作る場所の集約、通知、アルゴリズムの差し替え)と、設計の意図を短く伝える語彙としての役割は変わっていません。
デザインパターンとアーキテクチャパターンの違いは何ですか?
対象の粒度が違います。デザインパターンはクラスとオブジェクトの関係という小さな単位の型で、アーキテクチャパターン(MVC、レイヤードアーキテクチャ、クリーンアーキテクチャなど)はシステム全体の構成要素と責任の分け方を扱います。MVCの内部ではObserverやStrategyが部品として使われる、という入れ子の関係です。
デザインパターンは何から覚えればよいですか?
Strategy・Observer・Adapter・Factory Methodの4つから始めるのが効率的です。どれもフレームワークのAPIで頻繁に目にするうえ、Strategyを理解すればState・Template Methodとの違いへ、AdapterからDecorator・Proxy・Facadeとの違いへと、似たパターンをまとめて押さえられます。