Gobble up pudding

プログラミングの記事がメインのブログです。

MENU

第1回 生成に関するパターン Builder

スポンサードリンク

f:id:fa11enprince:20200701023026j:plain

Design Patterns 第1回 生成に関するパターン Builder

デザインパターンの本を買いましたが、
コードが断片化して書かれているためか理解できず……。
そんなとき、WikiBooks(CC-BY-SA 3.0)でいいサンプルを見つけたので、
翻訳して、ソースコードをちょっと変えて載せておきます。
適宜暇なときに修正・改定しておきます。

というわけでデザインパターン第1回目!
デザインパターン 生成に関するパターン Builderです。

生成に関するパターン

ソフトウェア工学では、生成に関するパターンは、オブジェクト生成メカニズムを扱うデザインパターンであり、その状況に適した方法でオブジェクトを生成しようとするものである。

オブジェクト生成の基本形は設計の問題を起こしたり設計を複雑にする。生成に関するパターンは、このオブジェクトの生成をどうにか管理することによってこの問題を解決する。
この本のこのセクションでは読み手が以前に紹介した関数、グローバル変数、スタックとヒープの違い、ポインタ、静的メンバー関数をよく理解していることを想定している。
見ての通りさまざまな生成に関するデザインパターンがあり、ソースコードをより抽象的にする特別な実装法を扱っていて、ひとつずつ説明する。

Builder

Builderは同一の生成の手順で異なる表現を生成できるように、複数のオブジェクトの構築をその表現から分離するのに使われる。

問題

複数のオブジェクトを生成したいが、複数のコンストラクタをメンバーに持ちたくないし、たくさんの引数を必要とするメンバーを持ちたくない。

解決法

クライアント側のコードでそのオブジェクトが利用できるようになる前に、中間オブジェクトを部分的に定義するメンバー関数が目的の中間オブジェクトを定義する。Builderパターンを使うことによって、生成に関するすべての選択肢が決定されるまでにオブジェクトの生成を先延ばしにすることができる。

サンプルソースコード

C++17でJavaとかでよく見るBuilderパターンぽくメソッドチェーンで書いてみました。
Cookの戻り値参照にし忘れてハマった。。。

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

// ピザ
class Pizza {
public:
    void setDough(const std::string& dough) {
        dough_ = dough;
    }
    void setSauce(const std::string& sauce) {
        sauce_ = sauce;
    }
    void setTopping(const std::string& topping) {
        topping_ = topping;
    }
    void open() const {
        std::cout << dough_ << "生地で、"
             << sauce_ << "ソースで、"
             << topping_ << "のトッピングのピザ" << std::endl;
    }

private:
    std::string dough_;   // パン生地
    std::string sauce_;   // ソース
    std::string topping_; // トッピング
};

// 抽象ピザBuilder
class AbstractPizzaBuilder {
public:
    virtual ~AbstractPizzaBuilder() {}
    std::unique_ptr<Pizza> getPizza() {
        return move(pizza_);
    }
    void createNewPizzaProduct() {
        pizza_ = std::make_unique<Pizza>();
    }
    virtual void buildDough() = 0;
    virtual void buildSauce() = 0;
    virtual void buildTopping() = 0;

protected:
    std::unique_ptr<Pizza> pizza_;
};

// ハワイアンピザBuilder
class HawaiianPizzaBuilder : public AbstractPizzaBuilder {
public:
    virtual ~HawaiianPizzaBuilder() override {}
    virtual void buildDough() override {
        pizza_->setDough("クロス");
    }
    virtual void buildSauce() override {
        pizza_->setSauce("マイルドな");
    }
    virtual void buildTopping() override {
        pizza_->setTopping("ハムとパイナップル");
    }
};

// スパイシーピザBuilder
class SpicyPizzaBuilder : public AbstractPizzaBuilder {
public:
    virtual ~SpicyPizzaBuilder() override {}
    virtual void buildDough() override {
        pizza_->setDough("焼いたパン");
    }
    virtual void buildSauce() override {
        pizza_->setSauce("辛い");
    }
    virtual void buildTopping() override {
        pizza_->setTopping("ペペロニとサラミ");
    }
};

// インスタンスを生成するクラス
class Cook {
public:
    Cook& setPizzaBuilder(std::unique_ptr<AbstractPizzaBuilder> pb) {
        pizzaBuilder_ = std::move(pb);
        return *this;
    }

    // 設定された型(クラス)に合わせて一気にインスタンスを生成し、さらにPizzaを返すクラス
    std::unique_ptr<Pizza> build() {
        pizzaBuilder_->createNewPizzaProduct(); // インスタンス生成
        pizzaBuilder_->buildDough();
        pizzaBuilder_->buildSauce();
        pizzaBuilder_->buildTopping();
        return pizzaBuilder_->getPizza();
    }

private:
    std::unique_ptr<AbstractPizzaBuilder> pizzaBuilder_;
};

int main() {
    Cook cook;
    std::unique_ptr<Pizza> hawaian = cook.setPizzaBuilder(std::make_unique<HawaiianPizzaBuilder>()).build();
    hawaian->open();
    std::unique_ptr<Pizza> spicy = cook.setPizzaBuilder(std::make_unique<SpicyPizzaBuilder>()).build();
    spicy->open();
}

実行結果(C++17)

$ ./builder.exe
クロス生地で、マイルドなソースで、ハムとパイナップルのトッピングのピザ
焼いたパン生地で、辛いソースで、ペペロニとサラミのトッピングのピザ

書籍

読んでもよくわからんかった本……文字が多すぎて眠いよ…zzz

次回のリンク

gup.monster
gup.monster