デザインパターン入門(モダン C++ 版)
GoF 23 のうち実務で本当によく出る 8 つを、モダン C++(std::function / shared_ptr / variant)で書き直します。旧来のクラス階層より簡潔です。
生成1. Singleton(一意インスタンス)
「全体で 1 つだけ」のインスタンス。Meyers' Singleton(C++11 以降)が最短・スレッドセーフ。
meyers.cpp
class Logger {
public:
static Logger& instance() {
static Logger inst; // C++11: 初回呼び出し時に 1 度だけ構築(スレッドセーフ)
return inst;
}
void log(const std::string& s);
private:
Logger() = default;
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
};
使いどころ: ロガー、設定オブジェクト、リソースマネージャ。ただし濫用すると依存関係が隠れて厄介。DI を検討。
生成2. Factory(生成の抽象化)
具体クラスを直接 new せず、生成関数を経由する。階層が変わっても呼び出し元を触らなくて済む。
factory.cpp
struct Shape { virtual double area() const = 0; virtual ~Shape() = default; };
struct Circle : Shape { ... };
struct Square : Shape { ... };
std::unique_ptr<Shape> makeShape(const std::string& kind) {
if (kind == "circle") return std::make_unique<Circle>();
if (kind == "square") return std::make_unique<Square>();
throw std::invalid_argument(kind);
}
現代的選択: 型の種類が静的に決まるなら std::variant + visit が代替になる。
構造3. Adapter(インターフェース変換)
既存クラスの API を、期待する API に包んで変換する。C の関数を C++ クラスに包むのも広義のこれ。
adapter.cpp
class CPrinter { // 使いたい既存 API
public: void printRaw(const char*) { ... }
};
class StringPrinter { // 欲しい API
CPrinter c_;
public:
void print(const std::string& s) { c_.printRaw(s.c_str()); }
};
構造4. Decorator(機能の積み重ね)
基底インターフェースを同じインターフェースで包み、振る舞いを追加。GUI のスクロールバー + 枠 + 影 みたいな積み重ね。
decorator.cpp
struct Stream { virtual void write(std::string) = 0; virtual ~Stream() = default; };
struct Compress : Stream {
std::unique_ptr<Stream> inner;
Compress(std::unique_ptr<Stream> s) : inner(std::move(s)) {}
void write(std::string s) override { inner->write(compress(s)); }
};
// Compress(Encrypt(File)) のような積み重ねが可能
振る舞い5. Strategy(差し替え可能な振る舞い)
アルゴリズムを差し替える。モダン C++ では virtual の代わりに std::function が楽。
strategy.cpp
class Sorter {
std::function<bool(int,int)> cmp_;
public:
Sorter(std::function<bool(int,int)> c) : cmp_(c) {}
void run(std::vector<int>& v) { std::sort(v.begin(), v.end(), cmp_); }
};
Sorter{[](int a, int b){ return a > b; }}; // 降順戦略
振る舞い6. Observer(イベント通知)
イベント源 → 複数リスナーに通知。std::function のコンテナで簡単に実装できる。
observer.cpp
template<class... Args>
class Signal {
std::vector<std::function<void(Args...)>> fs_;
public:
void connect(std::function<void(Args...)> f) { fs_.push_back(std::move(f)); }
void emit(Args... a) { for (auto& f : fs_) f(a...); }
};
Signal<int> onClick;
onClick.connect([](int id){ std::cout << id; });
onClick.emit(42);
振る舞い7. Command(操作のオブジェクト化)
操作を値として持ち回す。undo / redo の基礎。std::function で大部分カバー、複雑なら基底クラス。
command.cpp
struct Command {
std::function<void()> exec, undo;
};
std::stack<Command> history;
history.push({[&]{ x+=1; }, [&]{ x-=1; }});
history.top().exec();
振る舞い8. Visitor(型別分岐)
様々な型に対して処理を切り替える。モダン C++ では std::variant + std::visit が王道。継承+virtual より少ない記述で済む。
visitor.cpp
using Shape = std::variant<Circle, Square, Triangle>;
double area(const Shape& s) {
return std::visit([](const auto& x){ return x.area(); }, s);
}
// auto ラムダが C++17 では "generic visitor"
詳細は第 51 回 variant / any、および型消去(cpp-type-erasure)参照。