デザインパターン
繰り返し出てくる設計の形を、名前付きで再利用する。
デザインパターンとは
デザインパターンは、よくある設計上の問題に対する再利用可能な解決策です。 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に繋がる