GoFデザインパターン - ビルダー編
GoFコレクションにおけるビルダーパターン実践ガイド:概念、使いどころ、C++の実装例、注意点と出典を実用的に解説します
こんにちは!パン君です。
今回は GoF(Gang of Four)のデザインパターンのビルダー編になります。
サンプルコード(C++)、作り方、使うタイミング、注意点などを含めて実用的に解説します。
はじめに
ビルドパターンは複雑なオブジェクトの生成過程を分離して可読性・拡張性を高めることができます。
一次情報として Wikipedia の定義を引用します。
"ビルダー設計パターンの目的は、複雑なオブジェクトの構築とその表現を分離することである。これにより、同じ構築プロセスで異なる表現を生成できる。"
(出典: https://en.wikipedia.org/wiki/Builder_pattern)
en.wikipedia.orgBuilder pattern - Wikipedia
構造(要素)
典型的な構成は次の通りです。
Product:最終的に生成される複雑なオブジェクトBuilder:buildPartX()のようなステップを定義し、getResult()を持つConcreteBuilder:Builderを実装し、内部に可変な部品を持ち最終的なProductを返すDirector:構築の手順 を持ち、Builderに構築命令を出す。小さなケースでは Director を省くことも多い
C++ 実装例
以下は最小限の C++ 例です。Director を使ったパターンの典型例です。
// Product.h -- 生成対象
#pragma once
#include <string>
struct Product {
std::string partA;
std::string partB;
std::string partC;
};
// Builder.h -- 抽象ビルダー
#pragma once
#include <memory>
class Builder {
public:
virtual ~Builder() = default;
virtual void BuildPartA() = 0;
virtual void BuildPartB() = 0;
virtual void BuildPartC() = 0;
virtual std::unique_ptr<Product> GetResult() = 0;
};
// ConcreteBuilder.h
#pragma once
#include "Builder.h"
#include "Product.h"
#include <memory>
class ConcreteBuilder : public Builder {
private:
std::unique_ptr<Product> product_;
public:
ConcreteBuilder() : product_(std::make_unique<Product>()) {}
void BuildPartA() override { product_->partA = "Engine"; }
void BuildPartB() override { product_->partB = "Wheels"; }
void BuildPartC() override { product_->partC = "Body"; }
std::unique_ptr<Product> GetResult() override {
return std::move(product_);
}
};
// Director.h
#pragma once
#include "Builder.h"
class Director {
public:
void ConstructSportsCar(Builder& builder) {
builder.BuildPartA();
builder.BuildPartB();
builder.BuildPartC();
}
void ConstructToyCar(Builder& builder) {
builder.BuildPartB();
// Toy car doesn't need engine
}
};
// main.cpp
#include <iostream>
int main() {
ConcreteBuilder builder;
Director director;
director.ConstructSportsCar(builder);
auto product = builder.GetResult();
std::cout << "Product parts: " << product->partA << ", " << product->partB << ", " << product->partC << "\n";
}この実装のポイントは 構築手順 と 具体的構築 が分離されていることです。
ConcreteBuilder を差し替えるだけで多様性の実現が図れます。
いつ使うか
ビルダーを検討する代表ケース
- コンストラクタのパラメータが多く、ほとんどがオプションである場合(名前付き引数やファクトリでも代替可)
- 段階的または複雑な初期化が必要で、処理手順を分離したい場合(テスト用に段階的に検証できる)
- 同じ構築手順で異なる表現(複数の
ConcreteBuilder)を生成したい場合 - 不変オブジェクト(immutable)の生成を読みやすくしたい場合(Builder が可変の中間表現を組み立て最終生成で不変オブジェクトを返す)
代替手段
- 言語がサポートする named / optional parameters(簡潔なケース)
Factory Method/Abstract Factory(生成の責務をサブクラスやグループで管理したい場合)- コンストラクタの段階的補助関数(簡単なケースで十分なことが多い)
まとめ
Builder は 構築手順と表現の分離 を目的とした強力なパターンで、複雑な初期化・可変な表現を扱う場面で有効です。
しかし Builder はミュータブル性や DI との相性、テスト設計など考慮すべきトレードオフがあります。
導入前に影響範囲(生成元・シリアライズ・テスト)をした方がいいです。
参考・出典を記します
en.wikipedia.orgBuilder pattern - Wikipedia
それでは、次回は別の GoF パターンについても解説していきます。
この記事で提示したコードは学習目的に簡潔化しています。
実プロダクションで採用する際は要件に合わせて十分に検討してください
以上、パン君でした!