🗂

初詣に行ったらBuilderパターンが使えそうな場面に出くわした

2022/01/08に公開

皆さんはクレープはお好きだろうか。
お祭りとかにある屋台のやつ。
僕は大好きでつい先日初詣に行った時も屋台が出ていたので買おうとした。
クレープの醍醐味はやはりトッピングだろう。
無数にあるトッピングの中から何を選ぶか考えるのはワクワクする。
と同時にクレープをプログラムで作成するならどんな感じかなーとも考えていた。

例えばフルーツが苺とバナナ、その他トッピングにチョコ、生クリーム、カスタードクリーム、クリームチーズがあるとする。
クレープは基本どんなトッピングの組み合わせも可能なのでコンストラクタはメンバ変数全てを引数に取る一つだけにした。
コード化するとこんな感じだろうか。

zenn.java
class Crape{
    // トッピングは全て量で管理するためint型
    int amountOfStrawberry;
    int amountOfBanana;
    int amountOfChocolate;
    int amountOfFreshCream;
    int amountOfCustardCream;
    int amountOfCreamCheese;
    
    Crape(int amountOfStrawberry, int amountOfBanana, int amountOfChocolate, int amountOfFreshCream, int amountOfCustardCream, int amountOfCreamCheese){
	this.amountOfStrawberry = amountOfStrawberry;
	this.amountOfBanana = amountOfBanana;
	this.amountOfChocolate = amountOfChocolate;
	this.amountOfFreshCream = amountOfFreshCream;
	this.amountOfCustardCream = amountOfCustardCream;
	this.amountOfCreamCheese = amountOfCreamCheese;
    }
}

想定するクライアントサイドのコードは以下の感じだ。

zenn.java
public class Main{
    public static void main(String[] args){
        //苺生クリームが食べたい場合
        Crape crape = new Crape(5, 0, 0, 5, 0, 0);
	//チョコ生クリームカスタードクリームが食べたい場合
        Crape crape = new Crape(0, 0, 5, 5, 5, 0);
    }
}

自分で書いておいてあれだがこれはあまり良い設計ではないと思う。
クライアントが食べたいと思っていないトッピングの量もわざわざ指定しないといけない。
食べたいと思っているトッピングのみ指定できるのが理想だ。
じゃあ、その分コンストラクタを増やせば良いじゃんと思うかもしれないが組み合わせは無数にあるのだ。
例えば上記トッピングから2つだけ選べとかなら6C2で15通りなので力技でなんとかなったかもしれないが(それでも十二分に多いが)選ばれるトッピングが幾つになるかは分からない。
しかもコーンフレークなど新たにトッピングが増えることも十分に考えられるので上記の方法は現実的ではない。

こんな時に登場するのがBuilderパターンだ。
Builderパターンで作られるクラスの責務はただ一つ、対象のクラスのインスタンスを作成する。これに尽きる。
クライアントはこのBuilderクラスに対して一つずつトッピングを指定して追加し終わった段階でBuilderクラスがCrapeのインスタンスを作成する。
これをコード化するとこうなる。

zenn.java
public class Main{
    public static void main(String[] args){
        CrapeBuilder crapeBuilder = new CrapeBuilder();
        //苺生クリームが食べたい場合
        //苺と生クリームのトッピング
        crapeBuilder.addStrawberry();
        crapeBuilder.addFreshCream();
        //指定されたトッピングをもとにインスタンス化
        crapeBuilder.build();
        
        //チョコ生クリームカスタードクリームが食べたい場合
        //チョコと生クリームとカスタードクリームのトッピング
        crapeBuilder.addChocolate();
        crapeBuilder.addFreshCream();
        crapeBuilder.addCustardCream();
        //指定されたトッピングをもとにインスタンス化
        crapeBuilder.build();
    }
}

class Crape{
    // トッピングは全て量で管理するためint型
    int amountOfStrawberry;
    int amountOfBanana;
    int amountOfChocolate;
    int amountOfFreshCream;
    int amountOfCustardCream;
    int amountOfCreamCheese;
    
    Crape(int amountOfStrawberry, int amountOfBanana, int amountOfChocolate, int amountOfFreshCream, int amountOfCustardCream, int amountOfCreamCheese){
	this.amountOfStrawberry = amountOfStrawberry;
	this.amountOfBanana = amountOfBanana;
	this.amountOfChocolate = amountOfChocolate;
	this.amountOfFreshCream = amountOfFreshCream;
	this.amountOfCustardCream = amountOfCustardCream;
	this.amountOfCreamCheese = amountOfCreamCheese;
    }
}

class CrapeBuilder{
    // デフォルトのトッピングの量
    static final int defaultAmount = 0;

    int amountOfStrawberry;
    int amountOfBanana;
    int amountOfChocolate;
    int amountOfFreshCream;
    int amountOfCustardCream;
    int amountOfCreamCheese;
    
    CrapeBuilder(){
        reset();
    }
    
    //クライアントから要望があったトッピングを管理
    void addStrawberry(){
        amountOfStrawberry += 5;
    }
    void addBanana(){
        amountOfBanana += 5;
    }
    void addChocolate(){
        amountOfChocolate += 5;
    }
    void addFreshCream(){
        amountOfFreshCream += 5;
    }
    void addCustardCream(){
        amountOfCustardCream += 5;
    }
    void addCreamCheese(){
        amountOfCreamCheese += 5;
    }
    
    //指定されたトッピングをもとにCrapeインスタンスを作成
    Crape build(){
        Crape crape = new Crape(
	    amountOfStrawberry,
	    amountOfBanana,
	    amountOfChocolate,
                amountOfFreshCream,
                amountOfCustardCream,
                amountOfCreamCheese
        );
        reset();
        return crape;
    }
    
    //一度インスタンスを作ったらメンバ変数の値をリセットする。
    void reset(){
        this.amountOfStrawberry = CrapeBuilder.defaultAmount;
        this.amountOfBanana = CrapeBuilder.defaultAmount;
        this.amountOfChocolate = CrapeBuilder.defaultAmount;
        this.amountOfFreshCream = CrapeBuilder.defaultAmount;
        this.amountOfCustardCream = CrapeBuilder.defaultAmount;
        this.amountOfCreamCheese = CrapeBuilder.defaultAmount;
    }
}

クライアントの操作がより分かりやすくなったと思う。
また変更にも強くなる。
例えば新たにトッピングが増えるときは最初の設計だとCrapeのインスタンスが作成される全ての箇所に修正をいれなければならなかったがBuilderパターンの場合はインスタンスの作成の処理をそこに閉じ込めているので修正も用意だ。

以上が僕のBuilderパターンに対しての認識だ。
ここが違うなどの意見があったら優しいコメントが頂けると有り難いです😇

終わり。

Discussion