这个专题,我们将要学习一下设计模式。
序:为什么需要建造模式
PS:我们应该还记得工厂方法模式中,工厂方法模式是一个产品等级,而抽象工厂模式是多个产品等级的产品族。下面的例子中:电脑店(工厂方法模式),苹果专卖店(抽象工厂模式)
小悠(谄媚模式):小胖哥哥,我想买一台电脑Product
,什么品牌的好呀?
小胖(不屑模式):买电脑?还是自己组装build
好,你把电脑的各个零件Parts
买回来,自己组装就好了,我可以帮你装的。
小悠(崇拜模式):恩恩,还有我寝室的5个人,她们也想买电脑,小胖哥哥,你也能帮她们吗?
小胖(无语模式):emmm,这样吧,我给你推荐一个电脑店Factory
吧。打个电话就可以送货上门的。
小悠(不满模式):哼,你不是说自己组装好吗?
小胖(解释模式):这个电脑店Factory
很不错,他们会根据你买笔记本的需求。给你组装不同的电脑的。
- 电脑零件采购员(
Builder
)根据你们各自的电脑要求,采购回来不同的电脑零件ConcreteBuilder
。 - 组装员(
Director
)将采购回来的零件组装成电脑。
最后送到你们的手上。
小悠(开心模式):听起来不错,谢谢小胖哥哥。
工厂模式实现:获取对象;
建造模式实现:创建对象;
这个例子好像看起来是工厂模式在起作用,减少用户重复性的组装。把购买-组装过程放到了电脑店中。好像没建造模式什么事情。但是购买零件-组装,购买零件-组装......对象的不同就是
购买零件
属性的不同,但组装
过程是相同的,我们可以共用一个组装
过程,达到代码的复用。
什么时候使用建造模式:
- 需要生成的产品对象内部结构复杂;
- 需要生成的产品对象属性相互依赖;
- 需要生成的产品对象使用其他不易得到对象;
1. 什么是建造者模式
建造模式利用一个导演者(Director)
和具体建造者(Builder)
对象,将所需的零件一个个建造出来,从而建造出完整的产品对象。建造者模式将产品结构和产品零件
的建造过程隐藏起来,把対建造过程进行指挥和具体建造者的责任分割开,达成责任划分和封装的目的。
首先我们要明白的是:
- 抽象建造者角色
(Builder)
:抽象接口,规范产品对象各个组成成分。一般来说,产品对象的属性和建造方法的数目相符。此接口独立于应用程序的业务逻辑。
具体建造者角色
(ConcreteBuilder)
:(1)实现抽象建造者Builder
所声明的接口。(2)在建造过程完成后,提供产品实例。导演者角色
(Director)
:调用具体建造者角色以创建产品对象。产品角色
(Product)
:产品就是建造的复杂角色。
一般来说,每有一个产品类
,就有一个相应的具体建造者类
。这些产品
应当有一样数目的零件
,而每有一个零件
就相应地在所有的建造者角色
里面有一个建造方法。
2. 建造模式代码分析
建造模式分为两个很重要的部分:
-
Builder
接口:定义了如何构建各个部件。 -
Director
角色:负责整体的构建算法,通常是分步骤的构建产品。
建造模式的核心:在于分离构建算法和具体构造的实现,从而使得构建算法可重用,具体的构造实现可以方便扩展和切换,从而可以灵活地构造出不同的产品对象。
代码书写注意点:
抽象建造者角色:抽象方法交给了各个子类实现,为不同的产品定制不同的零件。具体方法自己实现。产品对象有多少属性,抽象建造者一般有多少建造方法。
具体建造者角色:实现抽象方法,定制子类产品。通过引用传递,输出产品实例。
导演者角色:将
具体建造者角色
传入,开始组装数据。对于用户实时传入的参数construct
方法需提供方法入参。产品角色:
具体创建者角色
中包含的对象,提供set方法,以设置参数。
假设有一个电子杂志系统,定期地向用户的电子邮件信箱发送电子杂志。用户可以通过网页订阅电子杂志,也可以通过网页结束订阅。当客户开始订阅时,系统发送一个电子邮件表示欢迎,当客户结束订阅时,系统发送一个电子邮件表示欢送。本例子就是这个系统负责发送“欢迎”和“欢送”邮件的模块。
简略点:
只有一个Message对象,定制欢送和欢迎的Message对象。
public abstract class AutoMessage {
//收件地址
private String toAddress;
//发件地址
private String fromAddress;
//标题
private String subject;
//内容
private String body;
//发送日期
private LocalDate sendData;
//为减少篇幅,get和set方法省略
}
1. 创建产品子类
//欢送消息
public class GoodbyeMessage extends AutoMessage {
public GoodbyeMessage() {
System.out.println("发送欢送消息");
}
}
//具体产品的父类
public class WelcomeMessage extends AutoMessage {
public WelcomeMessage() {
System.out.println("发送欢迎消息");
}
}
2. 创建抽象建造者角色
定制子类特有零件和公共零件。
//抽象建造者角色
public abstract class Builder {
//父类的message对象
protected AutoMessage message;
//子类定制信息
public abstract void buildSubject();
//子类定制信息
public abstract void buildBody();
//产品返回方法
public abstract AutoMessage getAutoMessage();
//发件地址--建造方法
public void buildToAddress(String toAddress) {
message.setToAddress(toAddress);
}
//收件地址 --建造方法
public void buildFromAddress(String fromAddress) {
message.setFromAddress(fromAddress);
}
//发送时间 -- 建造方法
public void buildSendData() {
message.setSendData(LocalDate.now());
}
}
3. 创建具体建造角色:
- 实现子类特有零件功能;
- 建造结束后,返回产品实例;
public class GoodbyeBuilder extends Builder {
public GoodbyeBuilder() {
message=new GoodbyeMessage();
}
@Override
public void buildSubject() {
message.setSubject("欢送标题");
}
@Override
public void buildBody() {
message.setBody("欢送内容");
}
@Override
public AutoMessage getAutoMessage() {
return message;
}
}
//具体建造者角色
public class WelcomeBuilder extends Builder {
public WelcomeBuilder() {
message=new WelcomeMessage();
}
@Override
public void buildSubject() {
message.setSubject("欢迎标题");
}
@Override
public void buildBody() {
message.setBody("欢迎内容");
}
@Override
public AutoMessage getAutoMessage() {
return message;
}
}
4. 导演者角色:
- 接收用户实时传递数据;
- 组装对象;
//导演者角色
public class Director {
Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
//调用各个零件的建造方法
public void construct(String toAddress, String fromAdress) {
builder.buildToAddress(toAddress);
builder.buildFromAddress(fromAdress);
builder.buildSubject();
builder.buildBody();
builder.buildSendData();
}
}
5. 测试代码
//测试方法
public class TestBuilder {
public static void main(String[] args) {
Builder builder=new WelcomeBuilder();
Director director = new Director(builder);
director.construct("toAddress:123.100.12.11","fromAddress:192.100.12.12");
//最后的对象是哪个?
WelcomeMessage autoMessage = (WelcomeMessage) builder.getAutoMessage();
System.out.println("建造者的消息:"+autoMessage);
}
}
需要注意:具体产品无需实现同一父类。
3. 建造者模式的变种
其实建造模式的核心就在于导演者角色。所以可以对建造模式进行一定的简化,因为我们使用建造模式就是创造某个复杂对象,因此适当的简化会使程序更加简洁。
(直接使用具体建造者)由于是
Builder
模式来创建某个对象,因此就没有必要再定义一个Builder
接口,直接提供一个具体的建造者类就可以了。(导演者的功能交由客户端完成)对于创建一个复杂,可能会有很多种不同的选择和步骤,我们也可以去掉
Director
类,将导演功能和Client
的功能进行合并,也就是说,Client
此时就是Director
,它用来指导构建器类去构建需要的复杂对象。
场景2:
考虑这样一个实际应用,要创建一个保险合同的对象,里面很多属性的值都有约束,要求创建出来的对象是满足这些约束规则的。约束规则比如:保险合同通常情况下可以和个人签订,也可以和某个公司签订,但是一份保险合同不能同时与个人和公司签订。
Client
可以使用Director
和Builder
完成对象的创建工作,但是实际上我们每创建一个新的产品,均要创造一个ConcreteBuilder
具体创建者类,实现特有的零件。对于实时的用户自定义功能,我们可以在Director
的construct
方法设置参数,以达到用户实时传入的目的。正如2. 建造模式代码分析
那样。用户在建造复杂对象时,或者对象的创建有顺序要求。那么我们可以将
组装
过程封装起来。组装
就是创建对象,构造方法
也是创建对象。故,可以将Director
的职责交托给私有的构造方法
。我们也没必要使用
抽象建造者
接口,直接使用具体建造者类
,在建造者类中可以返回构建完毕的产品对象
。Direcor
实时传入参数的问题,我们就交由具体构造者类
去提供方法实现,Cilent
负责传入。
温故知新:系统之间那点事-java内部类(我们为什么要设计内部类!)
- 静态内部类是一个独立的类,但是为什么使用静态内部类呢?(不要告诉我,你在开发中没有使用过内部类...)
这时候,我们就可以具体分析了,此时有产品类Product
和Builder
类,Builder
虽然可以单独存在吧,但是只能创建Product
产品,只能被对应的Product
使用。那么此时应该怎么办?把Builder
并入到Product
中,搞的Product
违反单一职责,如果把Builder
独立,那么又可以被其他类使用,,不符合我们设计的本意。那么可以将其变成Product.Builder
,其他类就不能使用Builder
了。
好了,那就静态内部类了!
源码如下:
//保险者类
public class InsuranceContract {
//保险合同编号
private String contractId;
//个人
private String personName;
//公司
private String companyName;
//保险生效日期
private long begintDate;
//保险失效日期
private long endDate;
//扩展数据
private String otherData;
//私有构造方法(导演者角色)
private InsuranceContract(ContractBuilder builder) {
this.contractId = builder.contractId;
this.personName = builder.personName;
this.companyName = builder.companyName;
this.begintDate = builder.beginDate;
this.endDate = builder.endDate;
this.otherData = builder.otherData;
}
//具体的建造者合并到了产品对象中了,这个静态内部类之为外部类提供服务
public static class ContractBuilder {
private String contractId;
private String personName;
private String companyName;
private long beginDate;
private long endDate;
private String otherData;
public ContractBuilder(String contractId, long beginDate, long endDate) {
this.contractId = contractId;
this.beginDate = beginDate;
this.endDate = endDate;
}
public ContractBuilder setPersonName(String personName) {
this.personName = personName;
return this;
}
//提供实时传入的接口
public ContractBuilder setCompanyName(String companyName) {
this.companyName = companyName;
return this;
}
public ContractBuilder setOtherData(String otherData) {
this.otherData = otherData;
return this;
}
//建造方法,构造对象并返回(在建造方法中进行数据校验)
public InsuranceContract build() {
if (contractId == null || contractId.trim().length() == 0) {
throw new IllegalArgumentException("contractId is null");
}
//判断个人或者公司是否为null
boolean signPerson = personName != null && personName.trim().length() > 0;
boolean signCompany = companyName != null && companyName.trim().length() > 0;
if (signPerson && personName == null) {
throw new IllegalArgumentException("companyName and personName 不能同时存在");
}
if (beginDate <= 0) {
throw new IllegalArgumentException("beginDate 不合法");
}
if (endDate <= 0) {
throw new IllegalArgumentException("endDate 不合法");
}
if (beginDate > endDate) {
throw new IllegalArgumentException("beginDate大于endDate");
}
return new InsuranceContract(this);
}
}
}
测试类:
//客户端生成对象
public class BuilderClient {
public static void main(String[] args) {
//创建构造器对象
InsuranceContract.ContractBuilder builder =
new InsuranceContract.ContractBuilder("XP20180324", 20180322, 20190330);
//设置需要的数据
InsuranceContract contract = builder.setPersonName("小胖").setOtherData("扩展数据").build();
System.out.println(contract);
}
}
4. RabbitMQ-建造模式-源码分析
我们在设置BasicProperties()时候,是不是要写这么复杂的引用链代码,是不是有时候感觉到一脸蒙蔽?
public class MQBuilder {
public static void main(String[] args) {
// 一个是replyTo设置回调队列,另一是correlationId(相关性Id)为每个队列设置唯一值
AMQP.BasicProperties props = new AMQP.BasicProperties().builder().correlationId("XP123")
.replyTo("replyQueueName").build();
}
}
那好我们就可以分析一下源码了。
正如温故知新——内部类中,我们知道静态内部类对象的创建方式:
外部类.内部类 XX=new 外部类.内部类();
- 那么我们分析其实
BasicProperties
就是一个静态内部类,最后的build
方法【返回的Product
产品】也就是new AMQP.basicProperties(XXX)
,完成具体建造类
的功能之一:返回建造完毕产品对象。
public AMQP.BasicProperties build() {
return new AMQP.BasicProperties(this.contentType,
this.contentEncoding, this.headers, this.deliveryMode, this.priority,
this.correlationId, this.replyTo, this.expiration, this.messageId,
this.timestamp, this.type, this.userId, this.appId, this.clusterId);
}
- 我们继续看
new AMQP.BasicProperties().builder()
这个调用链,其实就是创建了内部静态内build
对象。
Builder
里面包含了零件信息,用户可以将自己需求传入到Builder
对象,实现实时传入到Director
的效果。
public static final class Builder {
private String contentType;
private String contentEncoding;
private Map headers;
private Integer deliveryMode;
private Integer priority;
private String correlationId;
private String replyTo;
private String expiration;
private String messageId;
private Date timestamp;
private String type;
private String userId;
private String appId;
private String clusterId;
public Builder() {
}
public AMQP.BasicProperties.Builder contentType(String contentType) {
this.contentType = contentType;
return this;
}
public AMQP.BasicProperties.Builder contentEncoding(String contentEncoding) {
this.contentEncoding = contentEncoding;
return this;
}
public AMQP.BasicProperties.Builder headers(Map headers) {
this.headers = headers;
return this;
}
public AMQP.BasicProperties.Builder deliveryMode(Integer deliveryMode) {
this.deliveryMode = deliveryMode;
return this;
}
public AMQP.BasicProperties.Builder priority(Integer priority) {
this.priority = priority;
return this;
}
public AMQP.BasicProperties.Builder correlationId(String correlationId) {
this.correlationId = correlationId;
return this;
}
public AMQP.BasicProperties.Builder replyTo(String replyTo) {
this.replyTo = replyTo;
return this;
}
public AMQP.BasicProperties.Builder expiration(String expiration) {
this.expiration = expiration;
return this;
}
public AMQP.BasicProperties.Builder messageId(String messageId) {
this.messageId = messageId;
return this;
}
public AMQP.BasicProperties.Builder timestamp(Date timestamp) {
this.timestamp = timestamp;
return this;
}
public AMQP.BasicProperties.Builder type(String type) {
this.type = type;
return this;
}
public AMQP.BasicProperties.Builder userId(String userId) {
this.userId = userId;
return this;
}
public AMQP.BasicProperties.Builder appId(String appId) {
this.appId = appId;
return this;
}
public AMQP.BasicProperties.Builder clusterId(String clusterId) {
this.clusterId = clusterId;
return this;
}
public AMQP.BasicProperties build() {
return new AMQP.BasicProperties(this.contentType, this.contentEncoding, this.headers, this.deliveryMode, this.priority, this.correlationId, this.replyTo, this.expiration, this.messageId, this.timestamp, this.type, this.userId, this.appId, this.clusterId);
}
}
}
5. lombok的@Builder注解
在lombok插件中使用@Builder注解,就可以为POJO提供建造者模式:
源代码:
@Builder(toBuilder = true)
@Getter
public class Address {
private String address;
private String phone;
}
class反编译文件
package com.protoType.MySchema;
public class Address
{
private String address;
private String phone;
Address(String address, String phone)
{
this.address = address; this.phone = phone; }
public static AddressBuilder builder() { return new AddressBuilder(); }
public AddressBuilder toBuilder() { return new AddressBuilder().address(this.address).phone(this.phone); }
public String getAddress() { return this.address; }
public String getPhone() { return this.phone;
}
public static class AddressBuilder
{
private String address;
private String phone;
public AddressBuilder address(String address)
{
this.address = address; return this; }
public AddressBuilder phone(String phone) { this.phone = phone; return this; }
public Address build() { return new Address(this.address, this.phone); }
public String toString() { return "Address.AddressBuilder(address=" + this.address + ", phone=" + this.phone + ")";
}
}
}
我看可以看到,这是一个典型的建造者模式,使用静态内部类充当建造者角色,建造者角色包含产品的所有零件,用户组装零件,将信息保存到具体建造者中,组装完成,开始生成产品角色,即调用产品的构造方法。【注意:产品的构造方法为default访问权限】
6. 总结
建造者模式将对象的生成分成了两步。(1)建造轮子;(2)将轮子安装到车子上。但是也不要拘泥于固定的模板,对于一些复杂对象的创建过程,我们可以考虑使用建造者模式创建。创建的时候可以使用静态内部类创建。