建造(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);
    }

}

  这样的代码读起来也舒服,语义也更好理解。

相关文章
相关标签/搜索