秒懂設計模式之建造者模式(Builder pattern)

版權申明】非商業目的可自由轉載
博文地址:https://blog.csdn.net/ShuSheng0007/article/details/86619675
出自:shusheng007

前言

人在IT江湖飄,不懂設計模式咋裝逼?

但是沒寫過5萬行代碼談設計模式都是在瞎扯淡,咱先看看定義

In software engineering, a software design pattern is a general, reusable solution to a commonly occurring problem within a given context in software design. It is not a finished design that can be transformed directly into source or machine code. It is a description or template for how to solve a problem that can be used in many different situations. Design patterns are formalized best practices that the programmer can use to solve common problems when designing an application or system.
在軟件工程領域,設計模式是一套通用的可複用的解決方案,用來解決在軟件設計過程中產生的通用問題。它不是一個可以直接轉換成源代碼的設計,只是一套在軟件系統設計過程中程序員應該遵循的最佳實踐準則。

咋樣,這要是沒點工作經驗還搞個毛線,因爲你完全不明白在說啥!你就記住了,沒有設計模式,軟件照樣開發,就是在大型軟件系統開發及維護過程中就痛苦不堪,最後在不斷重構後你會發現尼瑪竟然用了好多設計模式。

工作中常常出現建造者模式的身影,所以整理記錄一下,希望對自己和其他人都有一個幫助。希望你讀完本文後可以在實際開發過程中用上Builder 模式,成功裝一把逼,書生我就甚是欣慰了。

定義

定義雖然基本沒有屌用,因爲大部分人都看不懂,但是還的說出來。。。

The intent of the Builder design pattern is to separate the construction of a complex object from its representation. By doing so the same construction process can create different representations.
將一個複雜對象的構建與其表示分離,使得同樣的構建過程可以創建不同的表示

使用場景

這個就非常重要了,因爲如果你學了個東西,都不知道用來解決什麼問題,你說有什麼用?理解使用場景的的重要性要遠高於你是不是會實現這個模式,因爲只要你知道什麼問題可以使用builder模式來解決,那你即使不會寫,也可以在調查相關資料後完成。
我不想說一些大而正確的術語來把你搞蒙,我們只針對具體的問題,至於延展性的思考,隨着你知識的增長,逐漸會明白的。延展閱讀

當一個類的構造函數參數個數超過4個,而且這些參數有些是可選的參數,考慮使用構造者模式。

解決的問題

當一個類的構造函數參數超過4個,而且這些參數有些是可選的時,我們通常有兩種辦法來構建它的對象。
例如我們現在有如下一個類計算機類Computer,其中cpu與ram是必填參數,而其他3個是可選參數,那麼我們如何構造這個類的實例呢,通常有兩種常用的方式:

public class Computer {
    private String cpu;//必須
    private String ram;//必須
    private int usbCount;//可選
    private String keyboard;//可選
    private String display;//可選
}

第一:摺疊構造函數模式(telescoping constructor pattern ),這個我們經常用,如下代碼所示

public class Computer {
     ...
    public Computer(String cpu, String ram) {
        this(cpu, ram, 0);
    }
    public Computer(String cpu, String ram, int usbCount) {
        this(cpu, ram, usbCount, "羅技鍵盤");
    }
    public Computer(String cpu, String ram, int usbCount, String keyboard) {
        this(cpu, ram, usbCount, keyboard, "三星顯示器");
    }
    public Computer(String cpu, String ram, int usbCount, String keyboard, String display) {
        this.cpu = cpu;
        this.ram = ram;
        this.usbCount = usbCount;
        this.keyboard = keyboard;
        this.display = display;
    }
}

第二種:Javabean 模式,如下所示

public class Computer {
        ...

    public String getCpu() {
        return cpu;
    }
    public void setCpu(String cpu) {
        this.cpu = cpu;
    }
    public String getRam() {
        return ram;
    }
    public void setRam(String ram) {
        this.ram = ram;
    }
    public int getUsbCount() {
        return usbCount;
    }
...
}

那麼這兩種方式有什麼弊端呢?
第一種主要是使用及閱讀不方便。你可以想象一下,當你要調用一個類的構造函數時,你首先要決定使用哪一個,然後裏面又是一堆參數,如果這些參數的類型很多又都一樣,你還要搞清楚這些參數的含義,很容易就傳混了。。。那酸爽誰用誰知道。
第二種方式在構建過程中對象的狀態容易發生變化,造成錯誤。因爲那個類中的屬性是分步設置的,所以就容易出錯。

爲了解決這兩個痛點,builder模式就橫空出世了。

如何實現

  1. 在Computer 中創建一個靜態內部類 Builder,然後將Computer 中的參數都複製到Builder類中。
  2. 在Computer中創建一個private的構造函數,參數爲Builder類型
  3. 在Builder中創建一個public的構造函數,參數爲Computer中必填的那些參數,cpu 和ram。
  4. 在Builder中創建設置函數,對Computer中那些可選參數進行賦值,返回值爲Builder類型的實例
  5. 在Builder中創建一個build()方法,在其中構建Computer的實例並返回

下面代碼就是最終的樣子

public class Computer {
    private String cpu;//必須
    private String ram;//必須
    private int usbCount;//可選
    private String keyboard;//可選
    private String display;//可選

    private Computer(Builder builder){
        this.cpu=builder.cpu;
        this.ram=builder.ram;
        this.usbCount=builder.usbCount;
        this.keyboard=builder.keyboard;
        this.display=builder.display;
    }
    public static class Builder{
        private String cpu;//必須
        private String ram;//必須
        private int usbCount;//可選
        private String keyboard;//可選
        private String display;//可選
       
        public Builder(String cup,String ram){
            this.cpu=cup;
            this.ram=ram;
        }

        public Builder setUsbCount(int usbCount) {
            this.usbCount = usbCount;
            return this;
        }
        public Builder setKeyboard(String keyboard) {
            this.keyboard = keyboard;
            return this;
        }
        public Builder setDisplay(String display) {
            this.display = display;
            return this;
        }        
        public Computer build(){
            return new Computer(this);
        }
    }
}

如何使用

在客戶端使用鏈式調用,一步一步的把對象構建出來。

        Computer computer=new Computer.Builder("因特爾","三星")
                .setDisplay("三星24寸")
                .setKeyboard("羅技")
                .setUsbCount(2)
                .build();

案例

構建者模式是一個非常實用而常見的創建類型的模式(creational design pattern),例如圖片處理框架Glide,網絡請求框架Retrofit等都使用了此模式。

擴展

其實上面的內容是Builder在Java中一種簡化的使用方式,經典的Builder 模式與其有一定的不同,如果沒有興趣的同學就可以不用往下讀了。

傳統Builder 模式

構建者模式UML圖如下所示
在這裏插入圖片描述
如上圖所示,builder模式有4個角色。

  • Product: 最終要生成的對象,例如 Computer實例。
  • Builder: 構建者的抽象基類(有時會使用接口代替)。其定義了構建Product的抽象步驟,其實體類需要實現這些步驟。其會包含一個用來返回最終產品的方法Product getProduct()
  • ConcreteBuilder: Builder的實現類。
  • Director: 決定如何構建最終產品的算法. 其會包含一個負責組裝的方法void Construct(Builder builder), 在這個方法中通過調用builder的方法,就可以設置builder,等設置完成後,就可以通過builder的 getProduct() 方法獲得最終的產品。

我們接下來將最開始的例子使用傳統方式來實現一遍。

第一步:我們的目標Computer類:

public class Computer {
    private String cpu;//必須
    private String ram;//必須
    private int usbCount;//可選
    private String keyboard;//可選
    private String display;//可選

    public Computer(String cpu, String ram) {
        this.cpu = cpu;
        this.ram = ram;
    }
    public void setUsbCount(int usbCount) {
        this.usbCount = usbCount;
    }
    public void setKeyboard(String keyboard) {
        this.keyboard = keyboard;
    }
    public void setDisplay(String display) {
        this.display = display;
    }
    @Override
    public String toString() {
        return "Computer{" +
                "cpu='" + cpu + '\'' +
                ", ram='" + ram + '\'' +
                ", usbCount=" + usbCount +
                ", keyboard='" + keyboard + '\'' +
                ", display='" + display + '\'' +
                '}';
    }
}

第二步:抽象構建者類

public abstract class ComputerBuilder {
    public abstract void setUsbCount();
    public abstract void setKeyboard();
    public abstract void setDisplay();

    public abstract Computer getComputer();
}

第三步:實體構建者類,我們可以根據要構建的產品種類產生多了實體構建者類,這裏我們需要構建兩種品牌的電腦,蘋果電腦和聯想電腦,所以我們生成了兩個實體構建者類。

蘋果電腦構建者類

public class MacComputerBuilder extends ComputerBuilder {
    private Computer computer;
    public MacComputerBuilder(String cpu, String ram) {
        computer = new Computer(cpu, ram);
    }
    @Override
    public void setUsbCount() {
        computer.setUsbCount(2);
    }
    @Override
    public void setKeyboard() {
        computer.setKeyboard("蘋果鍵盤");
    }
    @Override
    public void setDisplay() {
        computer.setDisplay("蘋果顯示器");
    }
    @Override
    public Computer getComputer() {
        return computer;
    }
}

聯想電腦構建者類

public class LenovoComputerBuilder extends ComputerBuilder {
    private Computer computer;
    public LenovoComputerBuilder(String cpu, String ram) {
        computer=new Computer(cpu,ram);
    }
    @Override
    public void setUsbCount() {
        computer.setUsbCount(4);
    }
    @Override
    public void setKeyboard() {
        computer.setKeyboard("聯想鍵盤");
    }
    @Override
    public void setDisplay() {
        computer.setDisplay("聯想顯示器");
    }
    @Override
    public Computer getComputer() {
        return computer;
    }
}

第四步:指導者類(Director)

public class ComputerDirector {
    public void makeComputer(ComputerBuilder builder){
        builder.setUsbCount();
        builder.setDisplay();
        builder.setKeyboard();
    }
}

使用

首先生成一個director (1),然後生成一個目標builder (2),接着使用director組裝builder (3),組裝完畢後使用builder創建產品實例 (4)。

public static void main(String[] args) {
        ComputerDirector director=new ComputerDirector();//1
        ComputerBuilder builder=new MacComputerBuilder("I5處理器","三星125");//2
        director.makeComputer(builder);//3
        Computer macComputer=builder.getComputer();//4
        System.out.println("mac computer:"+macComputer.toString());

        ComputerBuilder lenovoBuilder=new LenovoComputerBuilder("I7處理器","海力士222");
        director.makeComputer(lenovoBuilder);
        Computer lenovoComputer=lenovoBuilder.getComputer();
        System.out.println("lenovo computer:"+lenovoComputer.toString());
}

輸出結果如下:

mac computer:Computer{cpu='I5處理器', ram='三星125', usbCount=2, keyboard='蘋果鍵盤', display='蘋果顯示器'}
lenovo computer:Computer{cpu='I7處理器', ram='海力士222', usbCount=4, keyboard='聯想鍵盤', display='聯想顯示器'}

可以看到,文章最開始的使用方式是傳統builder模式的變種, 首先其省略了director 這個角色,將構建算法交給了client端,其次將builder 寫到了要構建的產品類裏面,最後採用了鏈式調用。

總結

設計模式值得你刻意練習!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章