デザインパターン

繰り返し出てくる設計の形を、名前付きで再利用する。

デザインパターンとは

デザインパターンは、よくある設計上の問題に対する再利用可能な解決策です。 C++ では、継承・多態性・委譲を組み合わせて表現します。

Singletonパターン

アプリ全体で1つだけ使いたいオブジェクトに使います。 ただし、乱用するとテストしにくくなります。

#include <iostream>
#include <string>

class Logger {
private:
    Logger() = default;

public:
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

    static Logger& getInstance() {
        static Logger instance;
        return instance;
    }

    void log(const std::string& msg) {
        std::cout << "[LOG] " << msg << std::endl;
    }
};

int main() {
    Logger::getInstance().log("アプリケーション開始");
    Logger::getInstance().log("処理実行中");
    return 0;
}

[LOG] アプリケーション開始
[LOG] 処理実行中

Factoryパターン

生成処理を隠して、利用側は「何が作られたか」だけを気にすればよくなります。

#include <iostream>
#include <memory>

class Animal {
public:
    virtual ~Animal() = default;
    virtual void sound() = 0;
};

class Dog : public Animal {
public:
    void sound() override {
        std::cout << "ワン" << std::endl;
    }
};

class Cat : public Animal {
public:
    void sound() override {
        std::cout << "ニャア" << std::endl;
    }
};

class AnimalFactory {
public:
    static std::unique_ptr<Animal> createAnimal(const std::string& type) {
        if (type == "dog") return std::make_unique<Dog>();
        if (type == "cat") return std::make_unique<Cat>();
        return nullptr;
    }
};

int main() {
    auto dog = AnimalFactory::createAnimal("dog");
    auto cat = AnimalFactory::createAnimal("cat");

    dog->sound();
    cat->sound();
    return 0;
}

ワン
ニャア

Observerパターン

状態の変化を、複数の受信側へ自動で通知したいときに使います。

#include <iostream>
#include <vector>
#include <memory>

class Observer {
public:
    virtual ~Observer() = default;
    virtual void update(const std::string& msg) = 0;
};

class Subject {
private:
    std::vector<Observer*> observers;

public:
    void attach(Observer* obs) {
        observers.push_back(obs);
    }

    void notify(const std::string& msg) {
        for (auto obs : observers) {
            obs->update(msg);
        }
    }
};

class ConcreteObserver : public Observer {
private:
    std::string name;

public:
    explicit ConcreteObserver(const std::string& n) : name(n) {}

    void update(const std::string& msg) override {
        std::cout << name << ": " << msg << std::endl;
    }
};

int main() {
    Subject subject;
    ConcreteObserver obs1("Observer1");
    ConcreteObserver obs2("Observer2");

    subject.attach(&obs1);
    subject.attach(&obs2);
    subject.notify("イベント発火");

    return 0;
}

Observer1: イベント発火
Observer2: イベント発火

Strategyパターン

動作の切り替えを、条件分岐ではなくオブジェクト差し替えで行います。

#include <iostream>
#include <memory>

class SortStrategy {
public:
    virtual ~SortStrategy() = default;
    virtual void sort(int arr[], int size) = 0;
};

class BubbleSort : public SortStrategy {
public:
    void sort(int arr[], int size) override {
        std::cout << "バブルソート実行" << std::endl;
    }
};

class QuickSort : public SortStrategy {
public:
    void sort(int arr[], int size) override {
        std::cout << "クイックソート実行" << std::endl;
    }
};

class SortContext {
private:
    std::unique_ptr<SortStrategy> strategy;

public:
    explicit SortContext(std::unique_ptr<SortStrategy> s)
        : strategy(std::move(s)) {}

    void setStrategy(std::unique_ptr<SortStrategy> s) {
        strategy = std::move(s);
    }

    void execute(int arr[], int size) {
        strategy->sort(arr, size);
    }
};

int main() {
    int data[] = {3, 1, 4, 1, 5};

    SortContext context(std::make_unique<BubbleSort>());
    context.execute(data, 5);
    context.setStrategy(std::make_unique<QuickSort>());
    context.execute(data, 5);

    return 0;
}

バブルソート実行
クイックソート実行

パターンの使い分け

  • Singleton: 共有リソースの管理に向くが、依存を隠しすぎない
  • Factory: 生成の分岐をまとめたいときに便利
  • Observer: イベント通知や状態変化の伝播に向く
  • Strategy: 振る舞いを差し替えたいときに向く

よくある誤り

誤り1: Singleton をグローバル変数代わりに使う

誤り2: Factory の分岐が増えすぎたまま放置する

誤り3: Observer の購読解除を考えない

ポイント

  • デザインパターンはコードの再利用ではなく設計の再利用
  • 生成・通知・切り替えの責務を分けると見通しがよくなる
  • パターンは目的に応じて使い分けるのが重要

やってみよう

練習1: ログ出力を Singleton ではなく DI に置き換える

練習2: 支払い方法を Strategy で切り替える

練習3: GUI の通知を Observer で整理する

まとめ

  • パターンは問題の形に合わせて使う
  • 実装よりも役割分担を意識すると理解しやすい
  • Phase 8 の残りは高度なOOPに繋がる