建造者模式——結合案例,通俗易懂


一個設計模式解決一類問題,最近學習了一下建造者模式,看了很多博客,講的模棱兩可,所以決定寫一下我覺得比較好理解的簡介
參考自知乎 https://zhuanlan.zhihu.com/p/58093669,

一、介紹

1、啥是建造者模式

是將一個復雜的對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示

看不懂對吧!其實我也看不懂,不管它,通過案例和代碼加深理解

2、使用場景

一個設計模式解決一類問題,那么建造者模式解決了什么問題呢?——對象的構建過於復雜的問題

  • 當一個類的構造函數參數過多(超過四個),並且有的參數可有可無,或者很多產品有默認值。
  • 產品類非常復雜或者產品類因為調用順序不同而產生不同作用
3、優點
  • 復雜產品的創建步驟分解在不同的方法中,這些方法可以調用順序不同,結果不同,創建結果很清晰
4、缺點
  • 如果產品的內部變化復雜,可能會導致需要定義很多具體建造者來實現這種變化。

二、案例

理論總是難以理解的,現在通過案例分析問題,一步步了解使用建造者模式的好處

【案例】好好看一下這個案例

KFC套餐
假如目前KFC里面有很多個套餐
在套餐里面有必點,也有選點,然后每個單品又有大小之分
必點:漢堡(hamburger),薯條(chips)
選點:雞腿(chicken),可樂(cola),披薩(pizza)

【用Java代碼模擬場景】

我們如何構成這么多套餐實例呢?

我們不使用建造者模式也能構建代碼,但是建造者模式會讓代碼看上去更裝逼,代碼到后期更結構化更容易維護和拓展

首先構建這個實體類KFC

public class KFC {
    //套餐必點
    private String hamburger;
    private String chips;
    
    //套餐選點
    private String chicken;
    private String cola;
    private String pizza;
}

我們的想法是不是折疊構造函數來創建實例,下面來嘗試一下

public class KFC{
    //省略了上面的屬性.....
    
    //必點套餐A
    public KFC(String hamburger,String chips){
        this(hamburger,chips,null,null,null);
    }
    A
	//套餐B
	public KFC(String hamburger,String chips,String chicken){
    	this(hamburger,chips,chicken,null,null);
	}
    //套餐C
	public KFC(String hamburger,String chips,String chicken,String cola){
    	this(hamburger,chips,chicken,cola,null);
	}
    
	//......還有好多種組合方式,你會發現使用折疊構造函數的方法十分復雜
    
	//全選
	public KFC(String hamburger,String chips,String chicken,String cola,String pizza){
    	this.hamburger = hamburger;
   	 	this.chips = chips;
    	this.chicken = chicken;
    	this.cola = cola;
    	this.pizza = pizza;
	}
}

我們會發現使用折疊構造函數的方式很復雜,很惡心,代碼看都不想看

那么有人會想,我可以使用set方法來創建,我只要一個必點構造就好了,那繼續模擬咯

public class KFC{
    //.....省略了屬性
    //必點
    public KFC(String hamburger,String chips){
        this.hamburger = hamburger;
        this.chips = chips;
    }
    //set方法
    public void setChicken(String chicken) {
        this.chicken = chicken;
    }

    public void setCola(String cola) {
        this.cola = cola;
    }

    public void setPizza(String pizza) {
        this.pizza = pizza;
    }
    
    //實例化對象,你會發現這種方式就友好很多
     public static void main(String[] args) {
        KFC kfc = new KFC("大漢堡","大薯條");
        //加小份可樂
        kfc.setCola("小可樂");
        //加個雞腿
        kfc.setChicken("大雞腿");
        System.out.println(kfc);
    }
}

你會發現使用set方式就友好了很多

這個雖然友好了很多,但是也有點小毛病,就是你set太隨意了,我可能這個套餐里面沒有這個單品,而使用set的人卻不知道,造成錯誤的套餐出現!。

為了解決上面的兩種問題:一種設計模式解決一類問題,所以建造者模式就出現了


三、建造者模式

先了解一下又哪些角色吧,看不懂沒關系,看一下代碼就懂了

四個角色

Product(產品角色): 一個具體的產品對象。

Builder(抽象建造者): 創建一個Product對象的各個部件指定的抽象接口。

ConcreteBuilder(具體建造者): 實現抽象接口,構建和裝配各個部件。

Director(指揮者): 構建一個使用Builder接口的對象。它主要是用於創建一個復雜的對象。它主要有兩個作用,一是:隔離了客戶與對象的生產過程,二是:負責控制產品對象的生產過程。

拿其中兩個套餐舉例

套餐A:漢堡,薯條,大雞腿
套餐B:漢堡,薯條,小雞腿,小可樂,小披薩

其中薯條和漢堡可大可小,並且必須有,
其它的都為固定大小,但是你可以選擇有或沒有

  • 產品(KFC)
public class KFC {
    //套餐必點
    private String hamburger;
    private String chips;
    
    //套餐選點
    private String chicken;
    private String cola;
    private String pizza;
    
        //必點
    public KFC(String hamburger,String chips){
        this.hamburger = hamburger;
        this.chips = chips;
    }
    //set方法
    public void setChicken(String chicken) {
        this.chicken = chicken;
    }

    public void setCola(String cola) {
        this.cola = cola;
    }

    public void setPizza(String pizza) {
        this.pizza = pizza;
    }
}
  • Builder

定義一個接口,表明需要建造什么,得到什么

public interface Builder {
    void setChicken();
    void setCola();
    void setPizza();
    KFC getKFC();
}
  • ConcreteBuilder:

此時應該注意,這個時候還沒有生產套餐,只是定義套餐

套餐A

public class ConcreteBuilder1 implements Builder {
    private KFC kfc;
    //這一步非常重要
    public ConcreteBuilder1(String hamburger,String chips){
        kfc = new KFC(hamburger,chips);
    }
    @Override
    public void setChicken() {
        kfc.setChicken("大雞腿");
    }

    @Override
    public void setCola() {
        kfc.setCola(null);
        System.out.println("套餐A里面沒有可樂");
    }

    @Override
    public void setPizza() {
        kfc.setPizza(null);
        System.out.println("套餐A里面沒有披薩");
    }

    @Override
    public KFC getKFC() {
        return kfc;
    }
}

套餐B

public class ConcreteBuilder2 implements Builder {
    private KFC kfc;
    //這一步非常重要
    public ConcreteBuilder2(String hamburger,String chips){
        kfc = new KFC(hamburger,chips);
    }
    @Override
    public void setChicken() {
        kfc.setChicken("小雞腿");
    }

    @Override
    public void setCola() {
        kfc.setCola("小可樂");
    }

    @Override
    public void setPizza() {
        kfc.setPizza("小披薩");
    }

    @Override
    public KFC getKFC() {
        return kfc;
    }
}

Director:

真正的執行者,這里把他當作服務員,此時你像服務員點餐

public class Director {
    public KFC build(Builder builder){
        //套餐里面我只選了雞腿和可樂
        builder.setChicken();
        builder.setCola();
        return builder.getKFC();
    }
}

測試

public class BuilderTest {
    public static void main(String[] args) {
       //套餐A
        System.out.println("======套餐A======");
        Builder concreteBuilder1 = new ConcreteBuilder1("大漢堡", "小薯條");
        KFC kfc1 = new Director().build(concreteBuilder1);
        System.out.println(kfc1);
        //套餐B
        System.out.println("======套餐B======");
        Builder concreteBuilder2 = new ConcreteBuilder2("小漢堡", "小薯條");
        KFC kfc2 = new Director().build(concreteBuilder2);
        System.out.println(kfc2);
    }
}

輸出

到了這里你還是會覺得有點麻煩,你會發現,單品可有可無的選擇上面你十分的被動,代碼看上去也很怪,如果你下次想全部單品先選上,再去選套餐的時候,你又要新建一個新的指導者。

我覺得普通的建造者模式不適合參數的可有可無的選擇,普通的建造者模式更側重調控次序,在有些情況下需要簡化系統結構


四、簡化版的建造者模式

這個時候簡化版的建造者模式站出來了

采用鏈式編程的方式

這種模式更加靈活,更加符合定義

既然Director是變化的,並且其實在生活中我們自己本身就是Director,所以這個時候我們可以把Director這個角色去掉,因為我們自身就是指導者

  • 產品(product)
public class KFC {
    //套餐必點
    private String hamburger;
    private String chips;

    //套餐選點
    private String chicken;
    private String cola;
    private String pizza;
    public KFC(String hamburger,String chips){
        this.hamburger = hamburger;
        this.hamburger = chips;
    }
    public void setChicken(String chicken) {
        this.chicken = chicken;
    }

    public void setCola(String cola) {
        this.cola = cola;
    }

    public void setPizza(String pizza) {
        this.pizza = pizza;
    }
}

  • 抽象建造者(builder)
public abstract class Builder {
        abstract Builder setChicken();
        abstract Builder setCola();
        abstract Builder setPizza();
        abstract KFC getKFC();
}
  • 具體建造者(ConcreteBuilder)
public class ConcreteBuilder extends Builder {
    KFC kfc;
    public ConcreteBuilder(String hamburger,String chips){
        kfc = new KFC(hamburger,chips);
    }
    @Override
    Builder setChicken() {
        kfc.setChicken("雞腿");
        return this;
    }

    @Override
    Builder setCola() {
        kfc.setCola("可樂");
        return this;
    }

    @Override
    Builder setPizza() {
        kfc.setPizza("披薩");
        return this;
    }

    @Override
    KFC getKFC() {
        return kfc;
    }
}

  • 測試
public class BTest {
    public static void main(String[] args) {
       KFC kfc = new ConcreteBuilder("漢堡","薯條").setChicken().setCola().getKFC();
    }
}

如果不需要抽象建造者的角色來規定生產內容,那么代碼到這里其實還有進一步的簡化空間。

【關鍵代碼】

使用靜態內部類的方式

【進一步簡化】

public class KFC {
    //套餐必點
    private String hamburger;
    private String chips;

    //套餐選點
    private String chicken;
    private String cola;
    private String pizza;
	
    //一定要有一個帶有Builder參數的建造者
    private KFC(Builder builder) {
        this.hamburger = builder.hamburger;
        this.chips = builder.chips;
        this.chicken = builder.chicken;
        this.cola = builder.cola;
        this.pizza = builder.pizza;
    }

    //注意必須為靜態內部類
    public static class Builder{
        //套餐必點
        private String hamburger;
        private String chips;

        //套餐選點
        private String chicken;
        private String cola;
        private String pizza;

        public Builder(String hamburger,String chips){
            this.hamburger = hamburger;
            this.chips = chips;
        }
        public Builder setChicken(){
            this.chicken = "小雞腿";
            return this;
        }
        public Builder setCola(){
            this.cola = "小可樂";
            return this;
        }
        public Builder setPizza(){
            this.pizza = "小披薩";
            return this;
        }
        
        //生成一個產品
        public KFC getKFC(){
            return new KFC(this);
        }
    }
}

測試

public class BuilderTest {
   public static void main(String[] args) {
       KFC kfc = new KFC.Builder("大漢堡", "小薯條").setChicken().setCola().getKFC();
       System.out.println(kfc);
    }
}

五、建造者模式和抽象工廠模式的區別

通過上面的代碼,你發現普通的建造者模式和抽象工廠模式真的很像,在建造者模式中的builder角色很像超級工廠,然后contracterBuilder很像具體的工廠,都是規定了建造的內容

那么它們之前 有什么區別呢

  • 建造者模式有指導者這個角色,直接返回一個組裝好的產品,而抽象工廠模式返回一系列相關的產品,這些產品位於不同的產品等級結構,構成了一個產品族
  • 建造者模式更適合復雜的產品構建
  • 可以將抽象工廠模式理解成汽車零件生產工廠,而建造者模式看出組裝工廠

【總結】

學習一類技巧是為了解決一類問題,學習設計模式主要是為了理解它的思想,將來遇到代碼的編寫,使用這種模式會更加的方式,而沒有必要刻意的去使用,但是需要刻意的去練習,形成這種思想


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM