閉じる

現場で本当に使うGoFデザインパターンまとめ

現場の開発では、GoFデザインパターンを全て完璧に覚える必要はありませんが、よく使うパターンを押さえておくことで、設計やリファクタリング、チーム開発のコミュニケーションが一気に楽になります。

本記事では「現場で本当に頻出する」パターンに絞って、具体的なイメージと簡単なコード例を交えながら解説します。

よく使うGoFデザインパターンの全体像

GoFの23パターンのうち、一般的な業務システムやWebアプリケーションでよく名前が挙がるのは次のあたりです。

  • 生成に関するパターン
    • Singleton
    • Factory Method
    • Abstract Factory
  • 構造に関するパターン
    • Adapter
    • Facade
    • Decorator
  • 振る舞いに関するパターン
    • Strategy
    • Observer
    • Template Method
    • Command

この記事では、この中でも「頻度が高く、理解しておくと得をしやすいパターン」に的を絞って説明していきます。

Singletonパターン

Singletonとは何か

Singletonパターンは「クラスのインスタンスを1個だけに制限し、どこからでも同じインスタンスにアクセスできるようにする」パターンです。

設定情報の管理、ログ出力クラス、接続プールなど、アプリケーション全体で共有すべきオブジェクトに用いられます。

実務では、フレームワーク側がDIコンテナなどで実質的なSingletonを提供していることも多く、自分でベタにSingletonを書く頻度は減ってきていますが、概念としては依然よく登場します。

Singletonのシンプルな実装例(C++風)

C++
#include <iostream>
#include <string>

// ロガークラスをSingletonとして定義
class Logger {
private:
    // 唯一のインスタンスへのポインタ
    static Logger* instance;

    // 外からnewできないようにコンストラクタをprivateにする
    Logger() {}

public:
    // コピーコンストラクタと代入演算子を禁止
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

    // インスタンス取得メソッド(グローバルアクセスポイント)
    static Logger* getInstance() {
        if (instance == nullptr) {
            instance = new Logger();
        }
        return instance;
    }

    // ログ出力メソッド
    void log(const std::string& message) {
        std::cout << "[LOG] " << message << std::endl;
    }
};

// 静的メンバ変数の定義
Logger* Logger::instance = nullptr;

int main() {
    // どこから呼んでも同じインスタンスが返される
    Logger* logger1 = Logger::getInstance();
    Logger* logger2 = Logger::getInstance();

    logger1->log("アプリケーション開始");
    logger2->log("同じインスタンスからログを出力");

    // ポインタが同じであることを確認
    if (logger1 == logger2) {
        std::cout << "logger1とlogger2は同じインスタンスです" << std::endl;
    }

    return 0;
}
実行結果
[LOG] アプリケーション開始
[LOG] 同じインスタンスからログを出力
logger1とlogger2は同じインスタンスです

Singletonを使うときの注意点

「どこからでもアクセスできる=グローバル変数に近い」という面があるため、テストがしづらくなったり、依存関係が見えづらくなったりします。

実務では次のような方針がよく取られます。

  • テスト可能性を重視する場合は、DIコンテナなどでスコープを管理し、明示的に依存を注入する
  • どうしてもグローバルな1インスタンスが必要な場合のみSingletonを使う

Factory Method / Abstract Factory

工場パターンのイメージ

Factory MethodとAbstract Factoryはどちらも「生成処理をカプセル化する」パターンです。

現場では、次のように理解しておくと整理しやすくなります。

  • Factory Method
    • 1種類の製品に対して、「どの具体クラスをnewするか」をサブクラス側に任せる
  • Abstract Factory
    • 関連する複数の製品群(ボタン+テキストボックスなど)を、まるごと切り替えられるようにする

フレームワークやライブラリが内部実装で使っていることが多く、「自分で0から書く」というよりは「コードリーディングでよく出会う」パターンです。

Factory Methodの簡単な例(Java風)

Java
// 製品インターフェース
interface Message {
    String getContent();
}

// 具体的な製品A
class HelloMessage implements Message {
    public String getContent() {
        return "Hello!";
    }
}

// 具体的な製品B
class ByeMessage implements Message {
    public String getContent() {
        return "Good bye!";
    }
}

// Creator(生成側の抽象クラス)
abstract class MessageCreator {
    // Factory Method: 具体的に何を作るかはサブクラスに任せる
    protected abstract Message createMessage();

    // 共通の処理フロー
    public void print() {
        Message msg = createMessage();
        System.out.println(msg.getContent());
    }
}

// 具体Creatorその1
class HelloMessageCreator extends MessageCreator {
    protected Message createMessage() {
        return new HelloMessage();
    }
}

// 具体Creatorその2
class ByeMessageCreator extends MessageCreator {
    protected Message createMessage() {
        return new ByeMessage();
    }
}

public class Main {
    public static void main(String[] args) {
        MessageCreator helloCreator = new HelloMessageCreator();
        MessageCreator byeCreator = new ByeMessageCreator();

        helloCreator.print(); // Hello!
        byeCreator.print();   // Good bye!
    }
}
実行結果
Hello!
Good bye!

「生成処理だけを差し替え、それ以外の処理フローは共通化したい」ときに役立ちます。

Strategyパターン

Strategyとは何か

Strategyパターンは「アルゴリズム(処理のやり方)をオブジェクトとして切り出し、実行時に差し替えできるようにする」パターンです。

実務では次のような場面でよく登場します。

  • 課金計算ロジックを契約プランごとに変えたい
  • 検索条件の組み合わせを、複数パターンから選べるようにしたい
  • ソートやフィルタリングの方針を、設定やユーザ操作で切り替えたい

Strategyのサンプル(C++)

C++
#include <iostream>
#include <memory>

// 戦略インターフェース
class DiscountStrategy {
public:
    virtual ~DiscountStrategy() = default;
    // 金額を受け取り、割引後の金額を返す
    virtual int apply(int price) const = 0;
};

// 具体戦略A: 定率割引(10%OFF)
class PercentageDiscount : public DiscountStrategy {
public:
    int apply(int price) const override {
        return static_cast<int>(price * 0.9); // 10%割引
    }
};

// 具体戦略B: 定額割引(500円OFF)
class FixedDiscount : public DiscountStrategy {
public:
    int apply(int price) const override {
        int discounted = price - 500;
        return discounted > 0 ? discounted : 0;
    }
};

// Context: 戦略を保持し、必要なときに呼び出す側
class PriceCalculator {
private:
    std::shared_ptr<DiscountStrategy> strategy;

public:
    // 戦略を差し替え可能にする
    void setStrategy(std::shared_ptr<DiscountStrategy> s) {
        strategy = s;
    }

    int calculate(int basePrice) const {
        if (!strategy) {
            // 戦略が設定されていない場合は割引なし
            return basePrice;
        }
        return strategy->apply(basePrice);
    }
};

int main() {
    PriceCalculator calculator;

    // プランA: 定率割引
    calculator.setStrategy(std::make_shared<PercentageDiscount>());
    std::cout << "定率割引後: " << calculator.calculate(3000) << "円" << std::endl;

    // プランB: 定額割引
    calculator.setStrategy(std::make_shared<FixedDiscount>());
    std::cout << "定額割引後: " << calculator.calculate(3000) << "円" << std::endl;

    // 割引なし
    calculator.setStrategy(nullptr);
    std::cout << "割引なし: " << calculator.calculate(3000) << "円" << std::endl;

    return 0;
}
実行結果
定率割引後: 2700円
定額割引後: 2500円
割引なし: 3000円

if文やswitch文でアルゴリズムが増殖してしまったとき、Strategyで外出しするとコードが整理されやすくなります

Template Methodパターン

Template Methodのイメージ

Template Methodパターンは「処理の流れ(テンプレート)を抽象クラスで固定し、細部の処理だけサブクラスに任せる」パターンです。

フレームワークのコールバックや、バッチ処理、データ読み込み処理などでよく見られます。

Template Methodのサンプル(Java)

Java
// 抽象クラス: テンプレートメソッドを持つ
abstract class DataImporter {

    // テンプレートメソッド: 処理の流れ(骨格)を定義
    public final void execute() {
        open();
        readData();
        close();
    }

    // 共通の前処理・後処理(必要ならオーバーライド可)
    protected void open() {
        System.out.println("接続を開きます");
    }

    protected void close() {
        System.out.println("接続を閉じます");
    }

    // 具体的な読み込み処理はサブクラスに任せる
    protected abstract void readData();
}

// CSVインポート用のサブクラス
class CsvImporter extends DataImporter {
    @Override
    protected void readData() {
        System.out.println("CSVファイルからデータを読み込みます");
    }
}

// APIインポート用のサブクラス
class ApiImporter extends DataImporter {
    @Override
    protected void readData() {
        System.out.println("外部APIからデータを取得します");
    }
}

public class TemplateMethodSample {
    public static void main(String[] args) {
        DataImporter csv = new CsvImporter();
        DataImporter api = new ApiImporter();

        csv.execute();
        System.out.println("----");
        api.execute();
    }
}
実行結果
接続を開きます
CSVファイルからデータを読み込みます
接続を閉じます
----
接続を開きます
外部APIからデータを取得します
接続を閉じます

「前後の共通処理は一箇所にまとめたいが、真ん中だけ用途ごとに変えたい」ときに、Template Methodはとても有効です。

Observerパターン

Observerとは何か

Observerパターンは「あるオブジェクトの状態変化を、複数のオブジェクトに自動的に通知する」パターンです。

GUIのイベントリスナや、リアクティブプログラミング、ドメインイベントなど、現場でかなり頻繁に見かける概念です。

Observerの簡単な例(C++)

C++
#include <iostream>
#include <vector>
#include <string>
#include <memory>

// 前方宣言
class Observer;

// Subject(観察される側)
class Subject {
private:
    std::vector<std::weak_ptr<Observer>> observers;
    std::string state;

public:
    void attach(std::shared_ptr<Observer> obs) {
        observers.push_back(obs);
    }

    void setState(const std::string& s);

    const std::string& getState() const {
        return state;
    }

    void notify();
};

// Observer(観察する側)のインターフェース
class Observer {
public:
    virtual ~Observer() = default;
    virtual void update(Subject& subject) = 0;
};

// 具体的Observer A
class ConsoleObserver : public Observer {
public:
    void update(Subject& subject) override {
        std::cout << "ConsoleObserver: state = " << subject.getState() << std::endl;
    }
};

// 具体的Observer B
class LengthObserver : public Observer {
public:
    void update(Subject& subject) override {
        std::cout << "LengthObserver: length = " 
                  << subject.getState().size() << std::endl;
    }
};

// Subjectのメソッド実装
void Subject::setState(const std::string& s) {
    state = s;
    notify(); // 状態変更時に通知
}

void Subject::notify() {
    for (auto& weakObs : observers) {
        if (auto obs = weakObs.lock()) {
            obs->update(*this);
        }
    }
}

int main() {
    Subject subject;

    auto consoleObs = std::make_shared<ConsoleObserver>();
    auto lengthObs = std::make_shared<LengthObserver>();

    subject.attach(consoleObs);
    subject.attach(lengthObs);

    subject.setState("Hello");
    subject.setState("Observer Pattern");

    return 0;
}
実行結果
ConsoleObserver: state = Hello
LengthObserver: length = 5
ConsoleObserver: state = Observer Pattern
LengthObserver: length = 15

GUIのイベントリスナや、JavaのPropertyChangeListenerなど、多くのフレームワークでObserverパターンが使われています。

Decoratorパターン

Decoratorのイメージ

Decoratorパターンは「元のオブジェクトをそのままラップして、振る舞いを少しずつ追加する」パターンです。

入出力ストリームや、Webフレームワークのミドルウェア、ログやキャッシュを追加するときなどに使われます。

Decoratorのサンプル(Java)

Java
// コンポーネントのインターフェース
interface Notifier {
    void send(String message);
}

// 基本の実装(メール通知)
class EmailNotifier implements Notifier {
    public void send(String message) {
        System.out.println("メール送信: " + message);
    }
}

// デコレータの抽象クラス
abstract class NotifierDecorator implements Notifier {
    protected Notifier wrappee; // ラップする対象

    public NotifierDecorator(Notifier notifier) {
        this.wrappee = notifier;
    }

    public void send(String message) {
        wrappee.send(message);
    }
}

// 具体的デコレータ1: SMSも送る
class SmsNotifier extends NotifierDecorator {
    public SmsNotifier(Notifier notifier) {
        super(notifier);
    }

    public void send(String message) {
        super.send(message); // もとの処理(メール)を実行
        System.out.println("SMS送信: " + message);
    }
}

// 具体的デコレータ2: Slackにも送る
class SlackNotifier extends NotifierDecorator {
    public SlackNotifier(Notifier notifier) {
        super(notifier);
    }

    public void send(String message) {
        super.send(message); // もとの処理を実行
        System.out.println("Slack送信: " + message);
    }
}

public class DecoratorSample {
    public static void main(String[] args) {
        // 基本はメールだけ
        Notifier notifier = new EmailNotifier();

        // メール + SMS
        notifier = new SmsNotifier(notifier);

        // メール + SMS + Slack
        notifier = new SlackNotifier(notifier);

        notifier.send("システム障害が発生しました");
    }
}
実行結果
メール送信: システム障害が発生しました
SMS送信: システム障害が発生しました
Slack送信: システム障害が発生しました

「継承でクラスを増やしすぎたくないが、機能を組み合わせて追加したい」ときにDecoratorが有効です。

Facadeパターン

Facadeとは何か

Facadeパターンは「複雑なサブシステム一式を、1つのシンプルな窓口クラスで隠す」パターンです。

現場では次のような場面で特に使われます。

  • 外部APIやレガシーライブラリへの複雑な呼び出し手順を、単純なメソッドで包みたい
  • 画面側からは、ドメインロジックを1つのサービスメソッドとして呼びたい

Facadeの簡単な例(Java)

Java
// 複雑なサブシステムA: 認証
class AuthService {
    public boolean authenticate(String user, String password) {
        System.out.println("ユーザ認証中...");
        return "user".equals(user) && "pass".equals(password);
    }
}

// 複雑なサブシステムB: 在庫チェック
class InventoryService {
    public boolean checkStock(String itemId) {
        System.out.println("在庫チェック中...");
        return true;
    }
}

// 複雑なサブシステムC: 決済
class PaymentService {
    public boolean pay(String user, int amount) {
        System.out.println("決済処理中...");
        return true;
    }
}

// Facade: 上記の複雑な処理をまとめる窓口
class OrderFacade {
    private AuthService auth = new AuthService();
    private InventoryService inventory = new InventoryService();
    private PaymentService payment = new PaymentService();

    public boolean placeOrder(String user, String password, String itemId, int amount) {
        if (!auth.authenticate(user, password)) {
            System.out.println("認証失敗");
            return false;
        }
        if (!inventory.checkStock(itemId)) {
            System.out.println("在庫不足");
            return false;
        }
        if (!payment.pay(user, amount)) {
            System.out.println("決済失敗");
            return false;
        }
        System.out.println("注文完了");
        return true;
    }
}

public class FacadeSample {
    public static void main(String[] args) {
        OrderFacade facade = new OrderFacade();
        facade.placeOrder("user", "pass", "ITEM-001", 1000);
    }
}
実行結果
ユーザ認証中...
在庫チェック中...
決済処理中...
注文完了

Facadeを使うと、画面や外部公開APIからは「1メソッド呼べば完結する」ように見せられるため、クライアント側コードが非常にシンプルになります。

Commandパターン

Commandとは何か

Commandパターンは「操作自体をオブジェクトとして表現し、キューイング・ログ・アンドゥなどをしやすくする」パターンです。

GUIのボタン操作、ジョブキュー、Undo/Redoなどでよく利用されます。

Commandのシンプルなサンプル(Java)

Java
// Commandインターフェース
interface Command {
    void execute();
}

// Receiver: 実際の処理を行うクラス
class TextEditor {
    private StringBuilder text = new StringBuilder();

    public void append(String str) {
        text.append(str);
    }

    public void print() {
        System.out.println(text.toString());
    }
}

// 具体的Command: 文字列を追加する
class AppendCommand implements Command {
    private TextEditor editor;
    private String textToAppend;

    public AppendCommand(TextEditor editor, String textToAppend) {
        this.editor = editor;
        this.textToAppend = textToAppend;
    }

    public void execute() {
        editor.append(textToAppend);
    }
}

// Invoker: Commandを発行する側
class CommandInvoker {
    public void submit(Command command) {
        // 実際にはここでキューに積んだりログを残したりできる
        command.execute();
    }
}

public class CommandSample {
    public static void main(String[] args) {
        TextEditor editor = new TextEditor();
        CommandInvoker invoker = new CommandInvoker();

        invoker.submit(new AppendCommand(editor, "Hello "));
        invoker.submit(new AppendCommand(editor, "Command "));
        invoker.submit(new AppendCommand(editor, "Pattern"));

        editor.print();
    }
}
実行結果
Hello Command Pattern

「処理内容を1つのオブジェクトに閉じ込めておきたい」とき、Commandパターンは有力な選択肢になります。

現場での優先度とざっくり使い分け

ざっくりとした「優先度」と「よくある用途」を表にまとめます。

パターン現場での頻度感主な用途・ポイント
Singleton設定、ログ、接続などの1インスタンス共有。ただし乱用注意。
Factory Method生成処理を差し替えたいとき。フレームワーク内部でよく利用。
Abstract Factory中〜低関連する製品群の生成をまとめて切り替えたい。
Strategyアルゴリズムを差し替えたい。if/switchの分岐整理。
Template Method処理フローは共通、中身だけ変えたい。フレームワークに多い。
Observer状態変化の通知。イベントリスナやリアクティブ処理。
Decorator中〜高機能を「後付け」したい。ログやキャッシュ、通知追加など。
Facade複雑な処理群を1つの窓口メソッドで隠したい。
Command操作をオブジェクトとして扱い、キューやUndoを実現したい。

最初に押さえると実務で得をしやすいのは「Strategy」「Template Method」「Observer」「Facade」「Decorator」あたりです。

Factory系はフレームワークやライブラリを読むときに理解できていれば十分、Singletonは原則控えめに、というバランス感覚を持っておくと良いです。

まとめ

GoFデザインパターンは全部で23個ありますが、現場で頻出するのはそのうちの一部です。

本記事で紹介したSingleton / Factory系 / Strategy / Template Method / Observer / Decorator / Facade / Commandを押さえておくだけでも、既存コードの理解力やリファクタリングの選択肢が大きく広がります。

重要なのは名称を暗記することではなく、「どんな問題に対して、どのパターンが効きそうか」をイメージできるようになることです。

日々の開発で「あ、この複雑なifをStrategyにできそう」「このごちゃごちゃした処理はFacadeで隠せそう」と少しずつ意識しながら使っていくことで、自然と身についていきます。

設計原則・パターン

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!