Java設計模式學習記錄-建造者模式


前言

今天周末,有小雨,正好也不用出門了,那就在家學習吧,經過了兩周的面試,拿到了幾個offer,但是都不是自己很想去的那種,要么就是幾個人的初創小公司,要么就是開發企業內部系統的這種傳統開發,感覺這種傳統開發已經不能給自己帶來多大的提升了,因為工作了這幾年這種系統經歷了不少了,整天的就是增刪改查。創業小公司已經不想再去了,工作了這幾年去的都是這種小公司,風險大,壓力大,節奏快,沒時間沉淀學習。上上家東家還欠我幾個月工資呢,就是因為創業公司資金鏈斷了,然后老板忽悠領導,領導再忽悠我們,后來實在發不出工資了,忽悠不住了,就大批大批的走人了。

所以現在很是糾結,大點公司又去不了小的公司還看不上,目前就是這么個高不成低不就的狀態,所以還是抓緊時間學習,充實自己吧,哪怕現在進不去稍微大點的公司,那經過努力的學習后說不定還是有機會的,但是不努力是一點機會都沒有的。

好了,言歸正傳,這次要介紹的是創建型設計模式的最后一個,建造者模式,這個模式其實我在平時開發中用的很多,只不過是用了這個模式的更深一種形式吧。后面我會介紹到這一部分內容的。

建造者模式

建造者模式能夠將一個復雜的構建與其表示相分離,使得同樣的構建過程可以創建不同的表示。這句話理解起來可能有點抽象,簡單來說就是調用相同的創建對象的方法(建造過程)可以創建出不同的對象。 

還是舉例來說明吧,如果說我要創建一部手機,我需要先制造手機的幾個核心部件,例如:屏幕、電池、聽筒、話筒、機身等。

public class MobilePhone {

    //手機屏幕
    private String screen;
    //電池
    private String battery;
    //話筒
    private String microphone;
    //聽筒
    private String phoneReceiver;
    //機身
    private String phoneBody;

    public String getScreen() {
        return screen;
    }

    public void setScreen(String screen) {
        this.screen = screen;
    }

    public String getBattery() {
        return battery;
    }

    public void setBattery(String battery) {
        this.battery = battery;
    }

    public String getMicrophone() {
        return microphone;
    }

    public void setMicrophone(String microphone) {
        this.microphone = microphone;
    }

    public String getPhoneReceiver() {
        return phoneReceiver;
    }

    public void setPhoneReceiver(String phoneReceiver) {
        this.phoneReceiver = phoneReceiver;
    }

    public String getPhoneBody() {
        return phoneBody;
    }

    public void setPhoneBody(String phoneBody) {
        this.phoneBody = phoneBody;
    }
}

每一部手機都是這個類的對象,在創建一部手機的時候都要保證這幾個核心部件的創建。所以創建手機是需要一個標准規范的,因為這幾個核心部件都可以是不同的型號,不同的型號的部件制造出來的手機也是不同的,這樣就有了下面建造規范的接口。

public interface IBuildPhone {

    /**
     * 建造手機屏幕
     */
    void buildScreen();

    /**
     * 建造手機電池
     */
    void buildBattery();

    /**
     * 建造手機聽筒
     */
    void buildMicrophone();

    /**
     * 建造手機話筒
     */
    void buildPhoneReceiver();

    /**
     * 建造手機機身
     */
    void buildPhoneBody();
}

有了規范了,就可以創建手機了,先創建一個iphoneX。

public class IPhoneX implements IBuildPhone {

    private MobilePhone mobilePhone;

    public IPhoneX(){
        mobilePhone =  new MobilePhone();
    }

    /**
     * 建造手機屏幕
     */
    @Override
    public void buildScreen() {
        mobilePhone.setScreen("OLED顯示屏");
    }

    /**
     * 建造手機電池
     */
    @Override
    public void buildBattery() {
        mobilePhone.setBattery("2700mAh電池容量");
    }

    /**
     * 建造手機聽筒
     */
    @Override
    public void buildMicrophone() {
        mobilePhone.setMicrophone("聽筒");
    }

    /**
     * 建造手機話筒
     */
    @Override
    public void buildPhoneReceiver() {
        mobilePhone.setPhoneReceiver("話筒");
    }

    /**
     * 建造手機機身
     */
    @Override
    public void buildPhoneBody() {
        mobilePhone.setPhoneBody("iphoneX機身");
    }

    /**
     * 創建手機
     * @return
     */
    public MobilePhone build(){
        return mobilePhone;
    }
}

創建手機的工具寫好了,下面就可以使用了。

public class Director {

    /**
     * 建造一部手機
     * @param buildPhone
     * @return
     */
    public MobilePhone createMobilePhone(IBuildPhone buildPhone){

        buildPhone.buildBattery();
        buildPhone.buildMicrophone();
        buildPhone.buildScreen();
        buildPhone.buildPhoneReceiver();
        buildPhone.buildPhoneBody();

        return buildPhone.createMobilePhone();
    }

    @Test
    public void thatTest(){
        System.out.println(JSON.toJSONString(createMobilePhone(new IPhoneX())));
    }
}

關鍵的方法在createMobilePhone()方法,這個方法接收一個IBuildPhone接口的對象,所以只要符合這個創建手機規范的對象都可以創建一部手機。createMobilePhone()方法可以接收new IPhoneX()這樣一個對象,也可以接收new IPhone8()、new FindX()等等。

具體使用方法在thatTest()方法中。這個方法的運行結果是:

{"battery":"2700mAh電池容量","microphone":"聽筒","phoneBody":"iphoneX機身","phoneReceiver":"話筒","screen":"OLED顯示屏"}

上面這個例子的實現過程就使用了我們今天要說的建造者模式,我們來分析一下建造者模式的結構。

如下圖:

在建造者模式的結構圖中包含如下4個角色。

Builder(抽象建造者):它(IBuildPhone)為創建一個產品的各個部件指定了標准,規定了要創建復雜對象需要創建哪些部分,並不直接創建對象的具體部分。

ConcreteBuilder(具體建造者):它實現了Builder接口(IPhoneX),實現各個部分的具體構造和裝配方法,定義並明確它所創建的復雜對象,也可以提供一個方法返回創建好的復雜產品對象。

Product(產品角色):它(MobilePhone)是被建造的復雜對象,包含多個組成部分,具體建造者創建該產品的內部表示並定義它的裝配過程。

Director(指揮者):指揮者(Director),它復雜安排復雜對象的建造次序,指揮者與抽象建造者之間存在關聯關系,可以在Director的方法中調用建造者對象的部件構造與裝配方法,完成建造復雜對象的任務。客戶端一般只需與Director進行交互。

建造者模式靈活使用

好了,建造者模式到這里就算是介紹完了,然后說一說我們平時在項目中是怎么使用建造者模式的。先說一下場景,我們一般在開發的過程中都是需要分層的,MVC這個不一般人都不陌生吧,Model-View-Controller。(我這里只是舉例子不一定真的項目中就這樣用)那我們的數據在每一層的傳輸過程中如果需要增加或刪除些額外的功能怎么實現呢?

還是舉例子吧,如下面一個實體類:

public class Person {
    
    private Long id;
    
    private String name;
    
    private int age;
    
    private String address;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

如果說這個類是一個orm框架需要的實體類,它最好的場景是只被后端的數據操作使用,但是controller中有一個add方法,這個方法是新增一個人員,add方法接收的參數是一個人員對象,但是這個對象和上面這個Person得屬性有些差別,例如這個對象里面有請求ip,以及這個對象中沒有id這個字段(id在數據庫中自增,所以前端不允許傳過來id )。這個時候就不能使用Person類的對象作為add的方法了,需要再創建一個類專門來給Controller使用。

如下代碼:

/**
 * Controller使用的參數類
 */
public class PersonVO {

    private String name;

    private int age;

    private String address;

    //ip地址
    private String requestIP;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getRequestIP() {
        return requestIP;
    }

    public void setRequestIP(String requestIP) {
        this.requestIP = requestIP;
    }
}

參數對象可以創建了, 但是PersonVO的對象是需要轉成Person的對象的,這樣才能插入到數據庫中(數據庫的insert方法的參數是Person對象)。這種轉換操作其實也簡單如下代碼:

public Person convert2Person(PersonVO personVO){

        Person person = new Person();

        person.setName(personVO.getName());
        person.setAge(personVO.getAge());
        person.setAddress(personVO.getAddress());
        
        return person;
    }

但是我們通常是不這么做的,因為如果要轉換的這個對象的字段很多那需要寫很多次對象調setter方法來進行賦值。一種方式是直接寫一個將所有屬性當做參數的構造方法,直接一個一個把屬性值傳入就可以了,這種方式最簡單暴力。還有一種方式就是需要包裝一下這種方式,把Person改造一下。

如下代碼:

public class Person {


    private Long id;

    private String name;

    private int age;

    private String address;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

     Person(){}
     
     Person(String name,int age,String address){
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public static Person builder(){
        return new Person();
    }

    public Person name(String name){
        this.name = name;
        return this;
    }

    public Person age(int age) {
        this.age = age;
        return this;
    }

    public Person address(String address) {
        this.address = address;
        return this;
    }

    public Person build(){
        return new Person(name,age,address);
    }
    
}

后面新增了兩個構造函數,以及一個builder()方法和一個build()方法,還有幾個賦值方法,需要注意的是賦值方法和setter方法的區別,這樣的賦值方法是在賦值后將當前對象返回,用來實現鏈式調用。

這樣在對象轉換的時候就可以這樣用了:

public Person convert2Person(PersonVO personVO){

        return Person.builder()
                .name(personVO.getName())
                .age(personVO.getAge())
          .address(personVO.getAddress()) .build(); }

這種方式其實也是一種建造者模式的應用,這種方式在構建對象的過程實現起來更靈活,例如如果這個對象就只有前兩個參數有值,address是沒有內容的,那可以直接這樣寫:

public Person convert2Person(PersonVO personVO){

        return Person.builder()
                .name(personVO.getName())
                .age(personVO.getAge())
                .build();
    }

在填充了兩個屬性后就直接調用build()方法區創建對象。

其實為了實現這種創建對象的方式,每次除了寫getter/setter方法后還需要寫這么多其他的代碼,這樣是有點麻煩的,所以在日常的開發過程中,我們是沒必要寫額外的代碼來實現這種方式,可以用工具來實現。推薦一個工具包Lombok,我們的開發工具是使用IDEA,IDEA在使用Lombok時是需要下載一個lombok的插件,然后在項目中依賴lombok的工具包,就可以使用了。使用了lombok后的代碼變的非常簡潔,連getter/setter方法都不用寫了。

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Person {


    private Long id;

    private String name;

    private int age;

    private String address;

}

@Data 這個注解代表實現了所有非final屬性的getter/setter方法,以及重寫了toString方法和hashCode方法。

@AllArgsConstructor 這個注解代表實現了一個包含全部屬性為參數的構造方法(Person(Long id,String name,int age, String address))。

@NoArgsConstructor 這個注解代表實現了一個沒有任何參數的構造方法(Person())。

@Builder 這個注解代表實現了上面介紹的那種靈活的創建對象的建造者模式(使用這個注解時需要依賴上面3個注解,原因看這種方式的實現過程就能明白了)。

在創建對象時,使用方式沒有變化也是鏈式調用方法賦值,這里就不再寫創建對象的過程了。

其實lombok還有一些其他的注解也很強大,使用這個工具包的好處是,不但使代碼變得簡潔,也提高了開發效率。

在這里想到了jQuery插件倡導的那個原則:“寫的更少,做的更多”

 

 

 

 

 

 

 

 

 


免責聲明!

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



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