在阎宏博士的《JAVA 与模式》一书中结尾是这样形容建造(Builder)模式的:
建造模式是对象的创立模式。建造模式能够将一个产品的内部表象(internal representation)与产品的生产过程宰割开来,从而能够使一个建造过程生成具备不同的内部表象的产品对象。
产品的内部表象
一个产品常有不同的组成成分作为产品的整机,这些整机有可能是对象,也有可能不是对象,它们通常又叫做产品的内部表象(internal representation)。不同的产品能够有不同的内部表象,也就是不同的整机。应用建造模式能够使客户端不须要晓得所生成的产品有哪些整机,每个产品的对应整机彼此有何不同,是怎么建造进去的,以及怎么组成产品。
对象性质的建造
有些状况下,一个对象会有一些重要的性质,在它们没有失当的值之前,对象不能作为一个残缺的产品应用。比方,一个电子邮件有发件人地址、收件人地址、主题、内容、附录等局部,而在最起码的收件人地址失去赋值之前,这个电子邮件不能发送。
有些状况下,一个对象的一些性质必须依照某个程序赋值才有意义。在某个性质没有赋值之前,另一个性质则无奈赋值。这些状况使得性质自身的建造波及到简单的商业逻辑。这时候,此对象相当于一个有待建造的产品,而对象的这些性质相当于产品的整机,建造产品的过程是建造整机的过程。因为建造整机的过程很简单,因而,这些整机的建造过程往往被“内部化”到另一个称做建造者的对象里,建造者对象返还给客户端的是一个全副整机都建造结束的产品对象。
建造模式利用一个导演者对象和具体建造者对象一个个地建造出所有的整机,从而建造出残缺的产品对象。建造者模式将产品的构造和产品的整机的建造过程对客户端暗藏起来,把对建造过程进行指挥的责任和具体建造者整机的责任宰割开来,达到责任划分和封装的目标。
建造模式的构造
在这个示意性的零碎里,最终产品 Product 只有两个整机,即 part1 和 part2。相应的建造办法也有两个:buildPart1() 和 buildPart2()、同时能够看出本模式波及到四个角色,它们别离是:
形象建造者(Builder)角色: 给 出一个形象接口,以标准产品对象的各个组成成分的建造。一般而言,此接口独立于应用程序的商业逻辑。模式中间接创立产品对象的是具体建造者 (ConcreteBuilder) 角色。具体建造者类必须实现这个接口所要求的两种办法:一种是建造办法 (buildPart1 和 buildPart2),另一种是返还构造办法 (retrieveResult)。一般来说,产品所蕴含的整机数目与建造办法的数目相符。换言之,有多少 整机,就有多少相应的建造办法。
具体建造者(ConcreteBuilder)角色: 负责这个角色的是与应用程序严密相干的一些类,它们在应用程序调用下创立产品的实例。这个角色要实现的工作包含:1. 实现形象建造者 Builder 所申明的接口,给出一步一步地实现创立产品实例的操作。2. 在建造过程实现后,提供产品的实例。
导演者(Director)角色: 负责这个角色的类调用具体建造者角色以创立产品对象。该当指出的是,导演者角色并没有产品类的具体常识,真正领有产品类的具体常识的是具体建造者角色。
产品(Product)角色: 产品便是建造中的简单对象。一般来说,一个零碎中会有多于一个的产品类,而且这些产品类并不一定有独特的接口,而齐全能够是不相关联的。
导演者角色是与客户端打交道的角色。导演者将客户端创立产品的申请划分为对各个整机的建造申请,再将这些申请委派给具体建造者角色。具体建造者角色是做具体建造工作的,然而却不为客户端所知。
一般来说,每有一个产品类,就有一个相应的具体建造者类。这些产品该当有一样数目的整机,而每有一个整机就相应地在所有的建造者角色里有一个建造办法。
源代码
产品类 Product
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;
}
}
形象建造者类 Builder
public interface Builder {public void buildPart1(); public void buildPart2(); public Product retrieveResult();
}
具体建造者类 ConcreteBuilder
public class ConcreteBuilder implements Builder {private Product product = new Product(); /**
- 产品整机建造办法 1
*/ @Override public void buildPart1() { // 构建产品的第一个整机 product.setPart1(“ 编号:9527”);
} /**
- 产品整机建造办法 2
*/ @Override public void buildPart2() { // 构建产品的第二个整机 product.setPart2(“ 名称:XXX”);
} /**
- 产品返还办法 */ @Override public Product retrieveResult() { return product;
}
}
导演者类 Director
public class Director {/**
- 持有以后须要应用的建造器对象 / private Builder builder; /*
- 构造方法,传入建造器对象 @param builder 建造器对象 / public Director(Builder builder){this.builder = builder;
} /**
- 产品构造方法,负责调用各个整机建造办法 */ public void construct(){
builder.buildPart1();
builder.buildPart2();
}
}
客户端类 Client
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());
}
}
时序图
客户端负责创立导演者和具体建造者对象。而后,客户端把具体建造者对象交给导演者,导演者操作具体建造者,开始创立产品。当产品实现后,建造者把产品返还给客户端。
把创立具体建造者对象的工作交给客户端而不是导演者对象,是为了将导演者对象与具体建造者对象的耦合变成动静的,从而使导演者对象能够操纵数个具体建造者对象中的任何一个。
应用场景
假如有一个电子杂志零碎,定期地向用户的电子邮件信箱发送电子杂志。用户能够通过网页订阅电子杂志,也能够通过网页完结订阅。当客户开始订阅时,零碎发送一个电子邮件示意欢送,当客户完结订阅时,零碎发送一个电子邮件示意欢迎。本例子就是这个零碎负责发送“欢送”和“欢迎”邮件的模块。
在本例中,产品类就是发给某个客户的“欢送”和“欢迎”邮件,如下图所示。
尽管在这个例子外面各个产品类均有一个独特的接口,但这仅仅是本例子特有的,并不代表建造模式的特点。建造模式能够利用到具备齐全不同接口的产品类上。大多数状况下是不晓得最终构建进去的产品是什么样的,所以在规范的建造模式外面,个别是不须要对产品定义形象接口的,因为最终结构的产品千差万别,给这些产品定义公共接口简直是没有意义的。
这个零碎含有客户端(Client)、导演者(Director)、形象建造者(Builder)、具体建造者(WelcomeBuilder 和 GoodbyeBuilder)、产品(WelcomeMessage 和 GoodbyeMessage)等角色。
源代码
抽象类 AutoMessage 源代码,send() 操作仅仅是示意性的,并没有给出任何发送电子邮件的代码。
public abstract class AutoMessage {// 收件人地址 private String to; // 发件人地址 private String from; // 题目 private String subject; // 内容 private String body; // 发送日期 private Date sendDate; public void send(){
System.out.println(“ 收件人地址:” + to);
System.out.println(“ 发件人地址:” + from);
System.out.println(“ 题目:” + subject);
System.out.println(“ 内容:” + body);
System.out.println(“ 发送日期:” + sendDate);
} public String getTo() { return to;
} public void setTo(String to) {this.to = to;
} public String getFrom() { return from;
} public void setFrom(String from) {this.from = from;
} 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 Date getSendDate() { return sendDate;
} public void setSendDate(Date sendDate) {this.sendDate = sendDate;
}
}
具体产品类 WelcomeMessage
public class WelcomeMessage extends AutoMessage {/**
- 结构子 */ public WelcomeMessage(){
System.out.println(“ 发送欢送信息 ”);
}
}
具体产品类 GoodbyeMessage
public class GoodbyeMessage extends AutoMessage{/**
- 结构子 */ public GoodbyeMessage(){
System.out.println(“ 发送欢迎信息 ”);
}
}
形象建造者类
public abstract class Builder {protected AutoMessage msg; // 题目整机的建造办法 public abstract void buildSubject(); // 内容整机的建造办法 public abstract void buildBody(); // 收件人整机的建造办法 public void buildTo(String to){
msg.setTo(to);
} // 发件人整机的建造办法 public void buildFrom(String from){
msg.setFrom(from);
} // 发送工夫整机的建造办法 public void buildSendDate(){
msg.setSendDate(new Date());
} /**
- 邮件产品实现后,用此办法发送邮件 此办法相当于产品返还办法 / public void sendMessage(){
msg.send();
}
}
具体建造者 WelcomeBuilder
public class WelcomeBuilder extends Builder {public WelcomeBuilder(){
msg = new WelcomeMessage();
}
@Override public void buildBody() { // TODO Auto-generated method stub msg.setBody(“ 欢送内容 ”);
}
@Override public void buildSubject() { // TODO Auto-generated method stub msg.setSubject(“ 欢送题目 ”);
}
}
具体建造者 GoodbyeBuilder
public class GoodbyeBuilder extends Builder {public GoodbyeBuilder(){
msg = new GoodbyeMessage();
}
@Override public void buildBody() { // TODO Auto-generated method stub msg.setBody(“ 欢迎内容 ”);
}
@Override public void buildSubject() { // TODO Auto-generated method stub msg.setSubject(“ 欢迎题目 ”);
}
}
导演者 Director,这个类提供一个 construct() 办法,此办法调用建造者的建造办法,包含 buildTo()、buildFrom()、buildSubject()、buildBody()、buildSendDate() 等,从而一部分一部分地建造出产品对象,既 AutoMessage 对象。
public class Director {
Builder builder; /**
- 结构子 */ public Director(Builder builder){this.builder = builder;
} /**
- 产品构造方法,负责调用各整机的建造办法 */ public void construct(String toAddress , String fromAddress){this.builder.buildTo(toAddress); this.builder.buildFrom(fromAddress); this.builder.buildSubject(); this.builder.buildBody(); this.builder.buildSendDate(); this.builder.sendMessage();
}
}
客户端 Client
public class Client {public static void main(String[] args) {// TODO Auto-generated method stub Builder builder = new WelcomeBuilder();
Director director = new Director(builder);
director.construct(“toAddress@126.com”, “fromAddress@126.com”);
}
}
建造模式分成两个很重要的局部:
- 一个局部是 Builder 接口,这里是定义了如何构建各个部件,也就是晓得每个部件性能如何实现,以及如何拆卸这些部件到产品中去;
- 另外一个局部是 Director,Director 是晓得如何组合来构建产品,也就是说 Director 负责整体的构建算法,而且通常是分步骤地来执行。
不论如何变动, 建造模式都存在这么两个局部,一个局部是部件结构和产品拆卸,另一个局部是整体构建的算法 。意识这点是很重要的,因为在源码交易建造模式中,强调的是固定整体构建的算法,而灵便扩大和切换部件的具体结构和产品拆卸的形式。
再直白点说,建造模式的重心在于拆散构建算法和具体的结构实现,从而使得构建算法能够重用。具体的结构实现能够很不便地扩大和切换,从而能够灵便地组合来结构出不同的产品对象。
应用建造模式构建简单对象
思考这样一个理论利用,要创立一个保险合同的对象,外面很多属性的值都有束缚,要求创立进去的对象是满足这些束缚规定的。束缚规定比方:保险合同通常状况下能够和集体签订,也能够和某个公司签订,然而一份保险合同不能同时与集体和公司签订。这个对象里有很多相似这样的束缚,采纳建造模式来构建简单的对象,通常会对建造模式进行肯定的简化,因为指标明确,就是创立某个简单对象,因而做适当简化会使程序更简洁。大抵简化如下:
● 因为是用 Builder 模式来创立某个对象,因而就没有必要再定义一个 Builder 接口,间接提供一个具体的建造者类就能够了。
● 对于创立一个简单的对象,可能会有很多种不同的抉择和步骤,罗唆去掉“导演者”,把导演者的性能和 Client 的性能合并起来,也就是说,Client 这个时候就相当于导演者,它来领导构建器类去构建须要的简单对象。
保险合同类
/**
- 保险合同对象 / public class InsuranceContract {// 保险合同编号 private String contractId; /*
- 被保险人员的名称,同一份保险合同,要么跟人员签订,要么跟公司签订 也就是说,“被保险人员”和“被保险公司”这两个属性,不可能同时有值 / private String personName; // 被保险公司的名称 private String companyName; // 保险开始失效日期 private long beginDate; // 保险生效日期,肯定会大于保险开始失效日期 private long endDate; // 其余数据 private String otherData; // 公有构造方法 private InsuranceContract(ConcreteBuilder builder){this.contractId = builder.contractId; this.personName = builder.personName; this.companyName = builder.companyName; this.beginDate = builder.beginDate; this.endDate = builder.endDate; this.otherData = builder.otherData;
} /**
- 保险合同的一些操作 */ public void someOperation(){
System.out.println(“ 以后正在操作的保险合同编号为【”+this.contractId+”】”);
} public static class ConcreteBuilder{private String contractId; private String personName; private String companyName; private long beginDate; private long endDate; private String otherData; /**
- 构造方法,传入必须要有的参数 @param contractId 保险合同编号 @param beginDate 保险合同开始失效日期 @param endDate 保险合同生效日期 / public ConcreteBuilder(String contractId,long beginDate,long endDate){this.contractId = contractId; this.beginDate = beginDate; this.endDate = endDate;
} // 被保险人员的名称 public ConcreteBuilder setPersonName(String personName) {this.personName = personName; return this;
} // 被保险公司的名称 public ConcreteBuilder setCompanyName(String companyName) {this.companyName = companyName; return this;
} // 其余数据 public ConcreteBuilder setOtherData(String otherData) {this.otherData = otherData; return this;
} /**
- 构建真正的对象并返回 @return 构建的保险合同对象 / public InsuranceContract build(){ if(contractId == null || contractId.trim().length()==0){throw new IllegalArgumentException(“ 合同编号不能为空 ”);
} boolean signPerson = (personName != null && personName.trim().length() > 0); boolean signCompany = (companyName != null && companyName.trim().length() > 0); if(signPerson && signCompany){throw new IllegalArgumentException(“ 一份保险合同不能同时与集体和公司签订 ”);
} if(signPerson == false && signCompany == false){throw new IllegalArgumentException(“ 一份保险合同不能没有签订对象 ”);
} if(beginDate <= 0){throw new IllegalArgumentException(“ 一份保险合同必须有开始失效的日期 ”);
} if(endDate <=0){throw new IllegalArgumentException(“ 一份保险合同必须有生效的日期 ”);
} if(endDate < beginDate){throw new IllegalArgumentException(“ 一份保险合同的生效日期必须大于失效日期 ”);
} return new InsuranceContract(this);
}
}
}
客户端类
public class Client {public static void main(String[]args){// 创立构建器对象 InsuranceContract.ConcreteBuilder builder = new InsuranceContract.ConcreteBuilder(“9527”, 123L, 456L); // 设置须要的数据,而后构建保险合同对象 InsuranceContract contract =
builder.setPersonName(“ 小明 ”).setOtherData(“test”).build(); // 操作保险合同对象的办法 contract.someOperation();
}
}
在本例中将具体建造者合并到了产品对象中,并将产品对象的构造函数私有化,避免客户端不应用构建器来构建产品对象,而是间接去应用 new 来构建产品对象所导致的问题。另外,这个构建器的性能就是为了创立被构建的对象,齐全能够不必独自一个类。
在什么状况下应用建造模式
- 须要生成的产品对象有简单的内部结构,每一个外部成分自身能够是对象,也能够仅仅是一个对象(即产品对象)的一个组成部分。
- 须要生成的产品对象的属性相互依赖。建造模式能够强制履行一种分步骤进行的建造过程,因而,如果产品对象的一个属性必须在另一个属性被赋值之后才能够被赋值,应用建造模式是一个很好的设计思维。
- 在对象创立过程中会应用到零碎中的其余一些对象,这些对象在产品对象的创立过程中不易失去。