天天看點

【設計模式】建立型模式—建造者模式(Builder Pattern)(四)前言一.建造者模式二.建造者模式角色三.建造者的代碼實作四.總結

Good-設計模式-003-不夠23種-kongfanyu

文章目錄

  • 前言
  • 一.建造者模式
  • 二.建造者模式角色
  • 三.建造者的代碼實作
    • 1.案例a:組裝電腦
      • 1.1.産品(Product)
      • 1.2.抽象建造者(Builder)
      • 1.3.具體建造者(ConcreteBuilder)
      • 1.4.建造指揮者(Director)
      • 1.5.測試
    • 2.案例b: 做蛋糕
      • 2.1.具體産品(Product)
      • 2.1.抽象建造者(Builder)
      • 2.3.具體建造者(ConcreteBuilder)
      • 2.4.建造指揮者(Director)
    • 3.案例c:做蛋糕-簡化版
    • 4.注意事項
  • 四.總結
    • 1.建造者模式優缺點
    • 2.建造者模式和工廠模式差別
    • 3.建構者模式在架構中的展現
      • 3.1. JDK的StringBuilder、StringBuffer、StringJoiner
      • 3.2.Tomcat中ServerEndpointConfig的建構
      • 3.3.MyBatis中SqlSessionFactoryBuilder
      • 3.4.Spring中也大量用到建構者模式

前言

建議先看這篇文章【設計模式】軟體設計7大原則以及23種設計模式初識(一)

一.建造者模式

生活中的場景

  • 去肯德基,漢堡、可樂、薯條、炸雞翅等是不變的,而其組合是經常變化的,不同的組合而成了不同的”套餐“
  • 計算機是由 CPU、主機闆、記憶體、硬碟、顯示卡、機箱、顯示器、鍵盤、滑鼠等部件組裝而成的,不同元件會組成不同的電腦
  • 遊戲中的不同角色,其`性别、個性、能力、臉型、體型、服裝、發型等特性都有所差異;
  • 汽車中的方向盤、發動機、車架、輪胎等部件也多種多樣;
  • 每封電子郵件的發件人、收件人、主題、内容、附件等内容也各不相同。
以上所有這些産品都是由多個部件構成的,各個部件可以靈活選擇,但其建立步驟都大同小異。這類産品的建立無法用前面介紹的工廠模式描述,隻有建造者模式可以很好地描述該類産品的建立,在這種場景下我們可以通過建造者模式來實作

建造者模式(Builder Pattern)也叫 “生成器模式”,”對象建構時非常複雜,且有很多步驟需要處理時,可以使用

多個簡單的對象一步一步組裝成這個複雜對象

建造模式的本質:

  1. 分離了對象子元件的

    單獨構造

    (由Builder來負責)和

    裝配

    (由Director負責)。 進而可以構造出複雜的對象。這個模式适用于:

    某個對象的建構過程複雜的情況下使用

  2. 由于實作了

    建構和裝配的解耦

    不同的建構器,相同的裝配

    ,也可以做出不同的對象;

    相同的建構器,不同的裝配順序

    也可以做出不同的對象。也就是實作了建構邏輯、裝配邏輯的解耦,實作了更好的複用

二.建造者模式角色

建造者模式的設計中,主要有

4個角色

  1. 産品(Product):一個

    具體

    的産品對象
  2. 抽象建造者(Builder):建造者的抽象類,規範産品對象的各個組成部分的建造,

    一般由子類實作具體建造過程

  3. 具體建造者(ConcreteBuilder):

    具體

    的建造者,根據不同的業務邏輯,具體到各個對象的各個組成部分的建造
  4. 建造指揮者(Director):

    調用具體的建造者

    來建立各個對象的各個部分,然後根據不同組合封裝起一個個方法供外界調用

核心思想

  1. 将複雜對象的建立過程(比如設定屬性值),交給

    建構者Builder去做

  2. 最後通過建構者的

    建構方法build()

    ,真正去建立對象。
  3. 建構方法裡調用

    需要執行個體化的類的構造函數

    ,并将建構者的屬性值複制給該對象。

    複制前

    可以做一些

    參數校驗

    預設值指派

    等操作。
    【設計模式】建立型模式—建造者模式(Builder Pattern)(四)前言一.建造者模式二.建造者模式角色三.建造者的代碼實作四.總結

三.建造者的代碼實作

引入依賴

<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
        </dependency>
           

1.案例a:組裝電腦

無論組裝一台什麼電腦的電腦總是需要包含以下部件:

  1. 需要一個主機
  2. 需要一個顯示器
  3. 需要一個鍵盤
  4. 需要一個滑鼠
  5. 需要音響等

    但每個部件都使用不同類型的牌子,如主機我用華碩的,顯示器用微軟的。。。。對于這個例子,我們就可以使用建造者模式組合成成一台電腦。

是以,我們可以将生成器模式了解為,假設我們有一個對象需要建立,這個對象是由多個元件(Component)組合而成,每個元件的建立都比較複雜,但運用元件來建立所需的對象非常簡單,可以使用建造者模式将建構複雜元件的步驟與運用元件建構對象分離

1.1.産品(Product)

具體的産品-電腦

@Getter
@Setter
@ToString
public class Computer {
    private String master;
    private String screen;
    private String keyboard;
    private String mouse;
    private String audio;
}
           

1.2.抽象建造者(Builder)

//抽象建造者-電腦建造者
public  abstract class AbstractComputerBuilder {
    protected Computer computer;

    public Computer getComputer() {
        return computer;
    }

    //構造電腦
    public void buildComputer() {
        this.computer = new Computer();
    }

    //構造主機
    public abstract AbstractComputerBuilder buildMaster(String master);

    //構造顯示器
    public abstract AbstractComputerBuilder buildScreen(String screen);

    //構造鍵盤
    public abstract AbstractComputerBuilder buildKeyboard(String keyBoard);

    //構造滑鼠
    public abstract AbstractComputerBuilder buildMouse(String mouse);

    //構造音響
    public abstract AbstractComputerBuilder buildAudio(String audio);
}
           

1.3.具體建造者(ConcreteBuilder)

//具體建造者-建構一台具體電腦
public  class SpecificComputerBuilder extends AbstractComputerBuilder {
    @Override
    public AbstractComputerBuilder buildMaster(String master) {
        this.computer.setMaster(master);
        return this;
    }

    @Override
    public AbstractComputerBuilder buildScreen(String screen) {
        this.computer.setScreen(screen);
        return this;
    }

    @Override
    public AbstractComputerBuilder buildKeyboard(String keyboard) {
        this.computer.setKeyboard(keyboard);
        return this;
    }

    @Override
    public AbstractComputerBuilder buildMouse(String mouse) {
        this.computer.setMouse(mouse);
        return this;
    }

    @Override
    public AbstractComputerBuilder buildAudio(String audio) {
        this.computer.setAudio(audio);
        return this;
    }
}
           

1.4.建造指揮者(Director)

本例會将不同組合封裝起一個個方法供外界調用,這也是一種最嚴格标準的寫法,隻适合于排列組合較少結果的産品。如果有很多種搭配,執行個體案例2的鍊式寫法比較合适,直接由使用者自己來選擇搭配。

public class Director {
    //電腦建造者
    private AbstractComputerBuilder computerBuilder;

    public void setComputerBuilder(AbstractComputerBuilder computerBuilder) {
        this.computerBuilder = computerBuilder;
    }

    public Computer getComputer() {
        return computerBuilder.getComputer();
    }

    /**
     * 産品建構與組裝方法-建構華為電腦
     */
    public void constructHuaweiComputer() {
        //執行個體化Computer
        computerBuilder.buildComputer();
        //鍊式指派
        computerBuilder.buildMaster("")
                .buildScreen("HUAWEI-顯示屏")
                .buildKeyboard("HUAWEI-黑軸機械鍵盤")
                .buildMouse("HUAWEI-滑鼠")
                .buildAudio("HUAWEI-音響");
    }

    /**
     * 産品建構與組裝方法-建構華為電腦
     */
    public void constructHpComputer() {
        //執行個體化Computer
        computerBuilder.buildComputer();
        //鍊式指派
        computerBuilder
                .buildMaster("HP-主機")
                .buildScreen("HP-顯示屏")
                .buildKeyboard("HP-黑軸機械鍵盤")
                .buildMouse("HP-滑鼠")
                .buildAudio("HP-音響");
    }
}
           

1.5.測試

public static void main(String[] args) {
        //建造指揮者
        Director director = new Director();

        //華為電腦建構者
        AbstractComputerBuilder computerBuilder = new SpecificComputerBuilder();
        //調用華為構造器
        director.setComputerBuilder(computerBuilder);

        //構造華為電腦
        director.constructHuaweiComputer();
        //擷取電腦
        Computer huaweiPc = director.getComputer();
        System.out.println(huaweiPc);

        //構造惠普電腦
        director.constructHpComputer();
        //擷取電腦
        Computer hpPc = director.getComputer();
        System.out.println(hpPc);
    }
           
【設計模式】建立型模式—建造者模式(Builder Pattern)(四)前言一.建造者模式二.建造者模式角色三.建造者的代碼實作四.總結

2.案例b: 做蛋糕

假如有一個蛋糕Cake對象,蛋糕這個對象有一個必選屬性size,還有一些可選屬性apple,banana,orange,mango等,

  • 必傳屬性代表使用者必須要指定蛋糕的大小,非必傳屬性代表這些蛋糕要加哪些材料。
  • 非必傳屬性組合可能特别多 并且 和必傳屬性間

    存在依賴關系 或者 有先後順序

    ,需要在構造蛋糕時需要

    對屬性進行校驗

2.1.具體産品(Product)

/**
 * 具體産品-蛋糕
 */
public class Cake {
    //蛋糕尺寸
    private int size;
    //加蘋果
    private String apple;
    //加香蕉
    private String banana;
    //加橘子
    private String orange;
    //加芒果
    private String mango;

    /**
     * 靜态方法,用于生成一個 Builder,這個不一定要有,不過寫這個方法是一個很好的習慣,
     * 有些代碼要求别人寫 new User.UserBuilder().a()...build() 看上去就沒那麼好
     */
    public static CakeBuilder builder() {
        return new CakeBuilder();
    }

    // 隻提供getter供外呼或者屬性值,不提供setter供外呼修改
    public int getSize() {return size;}
    public void setSize(int size) {this.size = size;}

    public String getApple() {return apple;}
    public void setApple(String apple) { this.apple = apple;}

    public String getBanana() { return banana;}
    public void setBanana(String banana) { this.banana = banana; }

    public String getOrange() { return orange;  }
    public void setOrange(String orange) {this.orange = orange; }

    public String getMango() {return mango; }
    public void setMango(String mango) { this.mango = mango;}

    @Override
    public String toString() {
        return "Cake [size=" + size + ", apple=" + apple + ", banana=" + banana + ", orange=" + orange + ", mango=" + mango + "]";
    }
}

           

2.1.抽象建造者(Builder)

/**
 * 抽象建造者
 */
public abstract class AbstractCakeBuilder {
    public abstract AbstractCakeBuilder size(int size);

    public abstract AbstractCakeBuilder apple(String apple);

    public abstract AbstractCakeBuilder orange(String orange);

    public abstract AbstractCakeBuilder banana(String banana);

    public abstract AbstractCakeBuilder mango(String mango);

    public abstract Cake build();
}
           

2.3.具體建造者(ConcreteBuilder)

CakeBuilder 繼承自AbstractCakeBuilder,實作了構造蛋糕的相關操作

/**
 * 外部類,專門用來與外界打交道
 * 核心是:先把所有的屬性都設定給 CakeBuilder,然後 build() 方法的時候,将這些屬性複制給實際産生的對象。
 */
public class CakeBuilder extends AbstractCakeBuilder {
    // 下面是和 Cake 一模一樣的一堆屬性
    //蛋糕尺寸
    private int size;
    //加蘋果
    private String apple;
    //加香蕉
    private String banana;
    //加橘子
    private String orange;
    //加芒果
    private String mango;


    // 鍊式調用設定各個屬性值,傳回 this,即 CakeBuilder
    @Override
    public AbstractCakeBuilder size(int size) {
        this.size = size;
        return this;
    }

    @Override
    public AbstractCakeBuilder apple(String apple) {
        this.apple = apple;
        return this;
    }

    @Override
    public AbstractCakeBuilder orange(String orange) {
        this.orange = orange;
        return this;
    }

    @Override
    public AbstractCakeBuilder banana(String banana) {
        this.banana = banana;
        return this;
    }

    @Override
    public AbstractCakeBuilder mango(String mango) {
        this.mango = mango;
        return this;
    }

    /**
     * build() 方法負責将 CakeBuilder 中設定好的屬性“複制”到 Cake 中。
     * 可以在 “複制” 之前對參數進行校驗,檢查參數之間的依賴關系是否正确
     */
    @Override
    public Cake build() {
        // 1. size 必傳
        if (size <= 0) {
            throw new RuntimeException("請正确設定蛋糕的尺寸");
        }

        // 2.apple/banana/orange/mango非必傳,但是具有依賴關系,即size >= 10 ,apple/banana/orange/mango 必須傳
        if (size >= 10) {
            if (apple == null || banana == null || orange == null || mango == null) {
                throw new RuntimeException("大于等于10磅的蛋糕,apple、banana、orange、mango必須要加");
            }
        }

        Cake cake = new Cake();
        cake.setSize(this.size);
        cake.setApple(this.apple);
        cake.setBanana(this.banana);
        cake.setOrange(this.orange);
        cake.setMango(this.mango);
        return cake;
    }
}

           

2.4.建造指揮者(Director)

通過 Cake.builder().xxx().xxx().build()自由組裝即可

public static void main(String[] args) {
        //鍊式程式設計
        Cake cake1 = Cake.builder()
                .size(10)
                .apple("apple")
                .orange("orange")
                .banana("banana")
                .mango("mango")
                .build();
        System.out.println(cake1);


        Cake cake2 = new CakeBuilder()
                .size(20)
                .apple("apple")
                .orange("orange")
                .banana("banana")
                .mango("mango")
                .build();
        System.out.println(cake2);
    }
           

測試結果

【設計模式】建立型模式—建造者模式(Builder Pattern)(四)前言一.建造者模式二.建造者模式角色三.建造者的代碼實作四.總結

3.案例c:做蛋糕-簡化版

/**
 * 具體産品-蛋糕
 */
public class Cake {
    //蛋糕尺寸
    private int size;
    //加蘋果
    private String apple;
    //加香蕉
    private String banana;
    //加橘子
    private String orange;
    //加芒果
    private String mango;

    /**
     * private,讓外面無法直接建立,并且将Builder的值指派給Cake
     * @param builder 蛋糕構造器
     */
    private Cake(CakeBuilder builder) {
        this.size = builder.size;
        this.apple = builder.apple;
        this.banana = builder.banana;
        this.orange = builder.orange;
        this.mango = builder.mango;
    }

    // 隻提供getter供外呼或者屬性值,不提供setter供外呼修改
    public int getSize() { return size; }
    public String getApple() { return apple; }
    public String getBanana() { return banana; }
    public String getOrange() { return orange; }
    public String getMango() { return mango; }

    /**
     * 靜态方法,用于生成一個 Builder,這個不一定要有,不過寫這個方法是一個很好的習慣,
     *      有些代碼要求别人寫 new User.UserBuilder().a()...build() 看上去就沒那麼好
     */
    public static CakeBuilder builder() {
        return new CakeBuilder();
    }

    @Override
    public String toString() {
        return "Cake [size=" + size + ", apple=" + apple + ", banana=" + banana + ", orange=" + orange + ", mango=" + mango + "]";
    }

    /**
     * 靜态内部類,專門用來與外界打交道
     *  核心是:先把所有的屬性都設定給 CakeBuilder,然後 build() 方法的時候,将這些屬性複制給實際産生的對象。
     */
    public static class CakeBuilder  {
        // 下面是和 Cake 一模一樣的一堆屬性
        //蛋糕尺寸
        private int size;
        //加蘋果
        private String apple;
        //加香蕉
        private String banana;
        //加橘子
        private String orange;
        //加芒果
        private String mango;


        // 鍊式調用設定各個屬性值,傳回 this,即 CakeBuilder
        public CakeBuilder size(int size) {
            this.size = size;
            return this;
        }

        public CakeBuilder apple(String apple) {
            this.apple = apple;
            return this;
        }

        public CakeBuilder orange(String orange) {
            this.orange = orange;
            return this;
        }

        public CakeBuilder banana(String banana) {
            this.banana = banana;
            return this;
        }

        public CakeBuilder mango(String mango) {
            this.mango = mango;
            return this;
        }

        /**
         * build() 方法負責将 CakeBuilder 中設定好的屬性“複制”到 Cake 中。
         *  可以在 “複制” 之前對參數進行校驗,檢查參數之間的依賴關系是否正确
         */
        public Cake build() {
            // 1. size 必傳
            if (size <= 0) {
                throw new RuntimeException("請正确設定蛋糕的尺寸");
            }

            // 2.apple/banana/orange/mango非必傳,但是具有依賴關系,即size >= 10 ,apple/banana/orange/mango 必須傳
            if (size >= 10) {
                if (apple == null || banana == null || orange == null || mango == null) {
                    throw new RuntimeException("大于等于10磅的蛋糕,apple、banana、orange、mango必須要加");
                }
            }
            return new Cake(this);
        }
    }

    public static void main(String[] args) {
        //鍊式程式設計
        Cake cake = Cake.builder()
                .size(10)
                .apple("apple")
                .orange("orange")
                .banana("banana")
                .mango("mango")
                .build();
        System.out.println(cake);
    }
}
           

4.注意事項

  1. Builder可以是内部類,也可以是外部類

    ,視情況而定,一般情況是作為

    具體産品的内部類

    ,這樣産品類多了,不至于多出很多Builder類,讓代碼結構變得複雜。
  2. 産品類的屬性需要

    複制一份到Builder

    ,并且要保持

    同步更

    新。
  3. Builder中指派方法需要

    傳回Builder本身

    ,達到

    鍊式調用

    的效果。
  4. Builder的

    build()方法中執行個體化産品類,并且做一些必要的參數校驗,非必填的參數校驗也可以放Builder的指派方法中

  5. Product産品類的

    構造函數設定成private的

    ,并且

    沒有對外提供set方法

    ,主要是為了

    建立産品類隻能通過Builder,并且建立出來的對象是不可修改的

  6. Product産品的

    構造函數的參數為Builder

    ,這樣為了

    友善将值複制給Product,也可以不用這樣做

四.總結

1.建造者模式優缺點

優點:

  1. 封裝性好,建立和使用分離
  2. 擴充性好,建造類之間獨立,一定程度上實作了解耦

缺點:

  1. 産生多餘的Builder對象
  2. 産品内部發生變化時,建造者都需要修改,成本較大

2.建造者模式和工廠模式差別

  1. 建造者模式根據

    不同的産品零件和順序

    可以創造出

    不同的産品

    ,而工廠模式建立出來的産品都是

    一樣的

    建構者模式是用來建立一種類型的複雜對象,通過設定不同的可選參數,“定制化”建立不同的對象。
  2. 建造者模式使用者需要知道這個産品有哪些零件組成,而工廠模式的使用者不需要知道,

    直接建立

    就行
  3. 工廠模式是用來建立

    不同但是相關類型的對象(繼承同一父類或者接口的一組子類)

    ,由給定的參數來決定建立哪種類型的對象,一般這個建立過程也比較簡單,如果複雜的話,可以工廠模式和建構者模式結合。

3.建構者模式在架構中的展現

3.1. JDK的StringBuilder、StringBuffer、StringJoiner

  • JDK中StringBuilder其實就是用了建構者模式,append拼接字元串,最後toString生成一個String對象。
StringBuilder sb = new StringBuilder();
sb .append("1").append("2").toString();
           

3.2.Tomcat中ServerEndpointConfig的建構

【設計模式】建立型模式—建造者模式(Builder Pattern)(四)前言一.建造者模式二.建造者模式角色三.建造者的代碼實作四.總結

3.3.MyBatis中SqlSessionFactoryBuilder

SqlSessionFactoryBuilder中隻有

多個build方法

,并且

參數直接從build中傳入

,這就是靈活應用,必填項有多種情況,那就對外提供多個不同參數清單的build,外界一看build方法就知道怎麼調用。

【設計模式】建立型模式—建造者模式(Builder Pattern)(四)前言一.建造者模式二.建造者模式角色三.建造者的代碼實作四.總結

3.4.Spring中也大量用到建構者模式

1. 比如

org.springframework.beans.factory.support.BeanDefinitionBuilder

,BeanDefinition中存在多個屬性,但是Spring沒有在BeanDefinitionBuilder再複制一遍,而是直接

new一個BeanDefinition給BeanDefinitionBuilder

,後面設定屬性時,也是直接設定給了BeanDefinition對象,這樣做的好處是減少重複代碼,靈活使用建構者模式。

【設計模式】建立型模式—建造者模式(Builder Pattern)(四)前言一.建造者模式二.建造者模式角色三.建造者的代碼實作四.總結

2. 再比如

org.springframework.messaging.support.MessageBuilder

,這個建構者模式就用的比較中規中矩了:

【設計模式】建立型模式—建造者模式(Builder Pattern)(四)前言一.建造者模式二.建造者模式角色三.建造者的代碼實作四.總結
【設計模式】建立型模式—建造者模式(Builder Pattern)(四)前言一.建造者模式二.建造者模式角色三.建造者的代碼實作四.總結

繼續閱讀