建造(Builder)模式

  建造模式能夠將一個產品的內部表象與產品的生成過程分割開來,從而可使一個建造過程生成具備不一樣的內部表象的產品對象。java

  摘自EffectiveJava:當構造方法參數過多時使用建造者模式。設計模式

產品的內部表象

  一個產品常有不一樣的組成成分做爲產品的零件,這些零件有多是對象,也有可能不是對象,它們一般又叫作產品的內部表象(internal representation)。不一樣的產品能夠有不一樣的內部表象,也就是不一樣的零件。使用建造模式可使客戶端不須要知道所生成的產品有哪些零件,每一個產品的對應零件彼此有何不一樣,是怎麼建造出來的,以及怎麼組成產品。ide

對象性質的建造

  有些狀況下,一個對象會有一些重要的性質,在它們沒有恰當的值以前,對象不能做爲一個完整的產品使用。好比,一個電子郵件有發件人地址、收件人地址、主題、內容、附錄等部分,而在最起碼的收件人地址獲得賦值以前,這個電子郵件不能發送。測試

  有些狀況下,一個對象的一些性質必須按照某個順序賦值纔有意義。在某個性質沒有賦值以前,另外一個性質則沒法賦值。這些狀況使得性質自己的建造涉及到複雜的商業邏輯。這時候,此對象至關於一個有待建造的產品,而對象的這些性質至關於產品的零件,建造產品的過程是建造零件的過程。因爲建造零件的過程很複雜,所以,這些零件的建造過程每每被「外部化」到另外一個稱作建造者的對象裏,建造者對象返還給客戶端的是一個所有零件都建造完畢的產品對象。ui

  建造模式利用一個導演者對象和具體建造者對象一個個地建造出全部的零件,從而建造出完整的產品對象。建造者模式將產品的結構和產品的零件的建造過程對客戶端隱藏起來,把對建造過程進行指揮的責任和具體建造者零件的責任分割開來,達到責任劃分和封裝的目的this

1. 簡單的構造模式

UML類圖:spa

 

代碼以下:設計

package cn.qlq.builder;

public class Product {

    /**
     * 定義一些關於產品的操做
     */
    private String part1;
    private String part2;

    public String getPart1() {
        return part1;
    }

    public void setPart1(String part1) {
        this.part1 = part1;
    }

    public String getPart2() {
        return part2;
    }

    public void setPart2(String part2) {
        this.part2 = part2;
    }
}
package cn.qlq.builder;

public interface Builder {

    public void buildPart1();

    public void buildPart2();

    public Product retrieveResult();
}

 

package cn.qlq.builder;

public class ConcreteBuilder implements Builder {

    private Product product = new Product();

    @Override
    public void buildPart1() {
        product.setPart1("part1");
    }

    @Override
    public void buildPart2() {
        product.setPart2("part2");
    }

    @Override
    public Product retrieveResult() {
        return product;
    }

}
package cn.qlq.builder;

public class Director {

    private Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }

    public void construct() {
        builder.buildPart1();
        builder.buildPart2();
    }

}

 

客戶端代碼:3d

package cn.qlq.builder;

public class Client {

    public static void main(String[] args) {
        Builder builder = new ConcreteBuilder();
        Director director = new Director(builder);
        director.construct();
        Product product = builder.retrieveResult();
        System.out.println(product.getPart1());
        System.out.println(product.getPart2());
    }

}

 

  上面的產品Product只有兩個零件,即part1和part2。相應的建造方法也有兩個:buildPart1()和buildPart2()、同時能夠看出本模式涉及到四個角色,它們分別是:code

  抽象建造者(Builder)角色:給 出一個抽象接口,以規範產品對象的各個組成成分的建造。通常而言,此接口獨立於應用程序的商業邏輯。模式中直接建立產品對象的是具體建造者 (ConcreteBuilder)角色。具體建造者類必須實現這個接口所要求的兩種方法:一種是建造方法(buildPart1和 buildPart2),另外一種是返還結構方法(retrieveResult)。通常來講,產品所包含的零件數目與建造方法的數目相符。換言之,有多少 零件,就有多少相應的建造方法。

  具體建造者(ConcreteBuilder)角色:擔任這個角色的是與應用程序緊密相關的一些類,它們在應用程序調用下建立產品的實例。這個角色要完成的任務包括:1.實現抽象建造者Builder所聲明的接口,給出一步一步地完成建立產品實例的操做。2.在建造過程完成後,提供產品的實例。

  導演者(Director)角色:擔任這個角色的類調用具體建造者角色以建立產品對象。應當指出的是,導演者角色並無產品類的具體知識,真正擁有產品類的具體知識的是具體建造者角色。

  產品(Product)角色:產品即是建造中的複雜對象。通常來講,一個系統中會有多於一個的產品類,並且這些產品類並不必定有共同的接口,而徹底能夠是不相關聯的

 

省略抽象建造者角色:

  若是隻有一個具體建造者的話能夠省略掉抽象建造者角色。

省略導演組角色:

  在具體建造者只有一個的狀況下,若是抽象者角色已經被省略掉,那麼導演者角色也能夠省略掉。

Builder就本身扮演了導演者和建造者的雙角色。此時須要改變Builder類,以下:

package cn.qlq.builder;

public class ConcreteBuilder {

    private Product product = new Product();

    public void buildPart1() {
        product.setPart1("part1");
    }

    public void buildPart2() {
        product.setPart2("part2");
    }

    public Product retrieveResult() {
        return product;
    }

    /**
     * 生產最終的產品
     * 
     * @return
     */
    public Product construct() {
        buildPart1();
        buildPart2();
        return product;
    }

}

試用場景:

(1)須要生成的產品具備複雜的內部結構,也就是成員屬性比較多

(2)須要生成的產品的屬性相互依賴時。建造者模式能夠強制實行一種分步驟進行的建造過程。

(3)在對象的建立過程當中會使用系統的一些其餘對象,這些對象在產品的建立過程當中不易獲得

 

優勢:

(1)建造者模式使得產品的內部表象能夠獨立地變化。使用建造者模式可使客戶端沒必要知道產品內部組成的細節

(2)每個builder都相互獨立而與其餘無關

(3)模式所建造的最終產品更易於控制。

 

建造者模式與抽象工程模式區別:

  在抽象工程模式中,每次調用工程都會返回一個完整的產品對象,客戶端決定使用這些產品組成一個或者多個更復雜產品對象。

  建造者模式則不一樣,它一點一點地建立出一個複雜的產品,而這個產品的組裝就發生在建造者角色內部。建造者模式的客戶端拿到的是一個完整的最後產品。

  抽象工廠模式和工廠模式都是設計模式,可是抽象工廠處在更加具體的尺度上,而建造者模式處在更加宏觀的尺度上。一個系統能夠由一個抽象工廠 + 一個建造模式組成,客戶端經過調用這個建造角色,間接地調用另外一個抽象工廠的工廠角色。工廠模式反還不一樣的產品,建造角色把他們組裝起來。

 

2.  建造者模式的應用

  假設有一個電子雜誌系統,按期地向用戶的電子郵件信箱發送電子雜誌。用戶能夠經過網頁訂閱電子雜誌,也能夠經過網頁結束訂閱。當客戶開始訂閱時,系統發送一個電子郵件表示歡迎,當客戶結束訂閱時,系統發送一個電子郵件表示歡送。本例子就是這個系統負責發送「歡迎」和「歡送」郵件的模塊。

  在本例中,產品類就是發給某個客戶的「歡迎」和「歡送」郵件。

UML類圖以下:

代碼以下:

package cn.qlq.builder;

import java.util.Date;

public abstract class AutoMessage {

    protected String subject = "";
    protected String body = "";
    protected String from = "";
    protected String to = "";
    protected Date sendDate = null;

    public AutoMessage() {
        super();
    }

    public void send() {
        System.out.println("subject - > " + subject);
        System.out.println("body - > " + body);
        System.out.println("from - > " + from);
        System.out.println("to - > " + to);

        System.out.println("發送郵件");
    }

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }

    public String getFrom() {
        return from;
    }

    public void setFrom(String from) {
        this.from = from;
    }

    public String getTo() {
        return to;
    }

    public void setTo(String to) {
        this.to = to;
    }

    public Date getSendDate() {
        return sendDate;
    }

    public void setSendDate(Date sendDate) {
        this.sendDate = sendDate;
    }

}
package cn.qlq.builder;

public class WelcomeMessage extends AutoMessage {

    public WelcomeMessage() {
        System.out.println("WelcomeMessage");
    }

    public void sayWelcome() {
        System.out.println("welcome ...");
    }

}
package cn.qlq.builder;

public class GoodbyeMsgBuilder extends Builder {

    public GoodbyeMsgBuilder() {
        autoMessage = new WelcomeMessage();
    }

    @Override
    void buildSubject() {
        autoMessage.setSubject("歡送郵件主體");
    }

    @Override
    void buildBody() {
        autoMessage.setBody("歡送郵件內容");
    }

}

 

package cn.qlq.builder;

import java.util.Date;

public abstract class Builder {

    protected AutoMessage autoMessage;

    abstract void buildSubject();

    abstract void buildBody();

    void buildFrom(String from) {
        autoMessage.setFrom(from);
    }

    void buildTo(String to) {
        autoMessage.setTo(to);
    }

    void buildSendDate() {
        autoMessage.setSendDate(new Date());
    }

    void sendMessage() {
        autoMessage.send();
    }
}
package cn.qlq.builder;

public class WelcomeMsgBuilder extends Builder {

    public WelcomeMsgBuilder() {
        autoMessage = new WelcomeMessage();
    }

    @Override
    void buildSubject() {
        autoMessage.setSubject("歡迎郵件主體");
    }

    @Override
    void buildBody() {
        autoMessage.setBody("歡迎郵件內容");
    }

}
package cn.qlq.builder;

public class GoodbyeMsgBuilder extends Builder {

    public GoodbyeMsgBuilder() {
        autoMessage = new WelcomeMessage();
    }

    @Override
    void buildSubject() {
        autoMessage.setSubject("歡送郵件主體");
    }

    @Override
    void buildBody() {
        autoMessage.setBody("歡送郵件內容");
    }

}

 

package cn.qlq.builder;

public class Director {

    Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }

    public void construct(String from, String to) {
        builder.buildFrom(from);
        builder.buildTo(to);
        builder.buildSendDate();
        builder.buildSubject();
        builder.buildBody();

        builder.sendMessage();
    }

    public Builder getBuilder() {
        return builder;
    }

    public void setBuilder(Builder builder) {
        this.builder = builder;
    }

}

 

測試代碼:

package cn.qlq.builder;

public class MainClass {

    public static void main(String[] args) {
        Builder builder = new WelcomeMsgBuilder();
        Director director = new Director(builder);

        director.construct("78987857@qq.com", "954185@qq.com");
    }

}

結果:

WelcomeMessage
subject - > 歡迎郵件主體
body - > 歡迎郵件內容
from - > 78987857@qq.com
to - > 954185@qq.com
發送郵件

 

3.  當構造方法參數過多時使用建造者模式

好比一個User,有好多屬性,可是隻有ID是必須有的,其餘屬性無關緊要。以下:

package cn.qlq.builder;

public class User {

    // 必須字段
    private int id;

    private String name;
    private String sex;
    private String job;
    private String health;
    private String BMI;
    private int height;
    private int weight;

    public User() {
        super();
    }

    public User(int id, String name, String sex, String job, String health, String bMI, int height, int weight) {
        super();
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.job = job;
        this.health = health;
        BMI = bMI;
        this.height = height;
        this.weight = weight;
    }

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    public String getHealth() {
        return health;
    }

    public void setHealth(String health) {
        this.health = health;
    }

    public String getBMI() {
        return BMI;
    }

    public void setBMI(String bMI) {
        BMI = bMI;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "User [id=" + id + ", name=" + name + ", sex=" + sex + ", job=" + job + ", health=" + health + ", BMI="
                + BMI + ", height=" + height + ", weight=" + weight + "]";
    }

}

 

當咱們設置幾個屬性的時候能夠經過構造方法進行建立,可是好比咱們只想設置一些屬性,其餘屬性沒用,咱們可能會寫成空值,這樣的代碼閱讀起來很難懂,屬性更多的時候更加難以閱讀:

User user = new User(1, "張三", "", "", "", "", 0, 0);

 

也有可能經過setter進行設值,以下:(屬性更多的時候須要更多的setter)

        User user = new User();
        user.setId(1);
        user.setName("xxx");
        user.setBMI("XXX");
    ...

 

解決辦法:採用建造模式 + 流式寫法

  因爲是用Builder模式來建立某個對象,所以就沒有必要再定義一個Builder接口,直接提供一個具體的建造者類就能夠了。
  對於建立一個複雜的對象,可能會有不少種不一樣的選擇和步驟,乾脆去掉「導演者」,把導演者的功能和Client的功能合併起來,也就是說,Client這個時候就至關於導演者,它來指導構建器類去構建須要的複雜對象。

package cn.qlq.builder;

public class UserBuilder {

    private User user = new User();

    /**
     * 構造方法確保ID必有
     * 
     * @param id
     */
    public UserBuilder(int id) {
        user.setId(id);
    }

    UserBuilder name(String name) {
        user.setName(name);
        return this;
    }

    UserBuilder sex(String sex) {
        user.setSex(sex);
        return this;
    }

    UserBuilder job(String job) {
        user.setJob(job);
        return this;
    }

    UserBuilder health(String health) {
        user.setHealth(health);
        return this;
    }

    UserBuilder BMI(String BMI) {
        user.setBMI(BMI);
        return this;
    }

    UserBuilder height(int height) {
        user.setHeight(height);
        return this;
    }

    UserBuilder weight(int weight) {
        user.setWeight(weight);
        return this;
    }

    public User build() {
        if (user.getId() == 0) {
            throw new RuntimeException("id必須設置");
        }

        return user;
    }

}

 

客戶端代碼:

package cn.qlq.builder;

public class MainClass {

    public static void main(String[] args) {
        UserBuilder userBuilder = new UserBuilder(2);
        User user = userBuilder.name("張三").BMI("xxx").health("健康").build();
        System.out.println(user);
    }

}

  這樣的代碼讀起來也舒服,語義也更好理解。

相關文章
相關標籤/搜索