(目录)
创建型设计模式——Builder 模式,中文一般叫建造者模式或生成器模式。 建造者模式的代码实现非常简单,原理掌握起来也不难,而难点就在于什么时候采用它。比如,经常会遇到的以下两个问题:
- 为什么直接使用构造函数或者使用 set 方法来创建对象不方便?
- 为什么一定需要建造者模式来创建?
建造者模式
建造者模式的定义是这样的:
将复杂对象的构造与其表示分离,以便同一构造过程可以创建不同的表示。
不⽤建造者有什么麻烦?
假设我们要⾃⼰开发⼀个RabbitMQ消息队列的客户端,有很多需要初 始化的参数,你会怎么做?
/**
* 基于构造⽅法为属性赋值⽆法适⽤于灵活多变的环境,且参数太⻓很难使⽤
*/
public class RabbitMQClientSample1 {
private String host = "127.0.0.1";
private int port = 5672;
private int mode;
private String exchange;
private String queue;
private boolean isDurable = true;
int connectionTimeout = 1000;
private RabbitMQClientSample1(String host, int port, int mode, String
exchange, String queue, boolean isDurable, int connectionTimeout) {
this.host = host;
this.port = port;
this.mode = mode;
this.exchange = exchange;
this.queue = queue;
this.isDurable = isDurable;
this.connectionTimeout = connectionTimeout;
if (mode == 1) { //⼯作队列模式不需要设置交换机,但queue必填
if (exchange != null) {
throw new RuntimeException("⼯作队列模式⽆须设计交换机");
}
if (queue == null || queue.trim().equals("")) {
throw new RuntimeException("⼯作队列模式必须设置队列名称");
}
if (isDurable == false) {
throw new RuntimeException("⼯作队列模式必须开启数据持久化");
}
} else if (mode == 2) { //路由模式必须设置交换机,但不能设置queue队列
if (exchange == null || exchange.trim().equals("")) {
throw new RuntimeException("路由模式请设置交换机");
}
if (queue != null) {
throw new RuntimeException("路由模式⽆须设置队列名称");
}
}
//其他各种验证
}
public void sendMessage(String msg) {
System.out.println("正在发送消息:" + msg);
}
public static void main(String[] args) {
//⾯对这么多参数恶不恶⼼?
RabbitMQClientSample1 client = new RabbitMQClientSample1("192.168.
31.210 ", 5672, 2, " sample - exchange ", null, true, 5000);
client.sendMessage("Test");
}
}
我的天,这么多参数要死啊,还是改⽤set⽅法灵活赋值吧
public class RabbitMQClientSample2 {
private String host = "127.0.0.1";
private int port = 5672;
private int mode;
private String exchange;
private String queue;
private boolean isDurable = true;
int connectionTimeout = 20;
//让对象不可变
private RabbitMQClientSample2() {
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public int getMode() {
return mode;
}
public void setMode(int mode) {
this.mode = mode;
}
public String getExchange() {
return exchange;
}
public void setExchange(String exchange) {
if (mode == 1) { //⼯作队列模式不需要设置交换机,但queue必填
if (exchange != null) {
throw new RuntimeException("⼯作队列模式⽆须设计交换机");
}
if (queue == null || queue.trim().equals("")) {
throw new RuntimeException("⼯作队列模式必须设置队列名称");
}
if (isDurable == false) {
throw new RuntimeException("⼯作队列模式必须开启数据持久化");
}
} else if (mode == 2) { //路由模式必须设置交换机,但不能设置queue队列
if (exchange == null || exchange.trim().equals("")) {
throw new RuntimeException("路由模式请设置交换机");
}
if (queue != null) {
throw new RuntimeException("路由模式⽆须设置队列名称");
}
}
this.exchange = exchange;
}
public String getQueue() {
return queue;
}
public void setQueue(String queue) {
if (mode == 1) { //⼯作队列模式不需要设置交换机,但queue必填
if (exchange != null) {
throw new RuntimeException("⼯作队列模式⽆须设计交换机");
}
if (queue == null || queue.trim().equals("")) {
throw new RuntimeException("⼯作队列模式必须设置队列名称");
}
if (isDurable == false) {
throw new RuntimeException("⼯作队列模式必须开启数据持久化");
}
} else if (mode == 2) { //路由模式必须设置交换机,但不能设置queue队列
if (exchange == null || exchange.trim().equals("")) {
throw new RuntimeException("路由模式请设置交换机");
}
if (queue != null) {
throw new RuntimeException("路由模式⽆须设置队列名称");
}
}
this.queue = queue;
}
public boolean isDurable() {
return isDurable;
}
public void setDurable(boolean durable) {
isDurable = durable;
}
public int getConnectionTimeout() {
return connectionTimeout;
}
public void setConnectionTimeout(int connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
//没办法,必须增加⼀个额外的validate⽅法验证对象是否复合要求
public boolean validate() {
if (mode == 1) { //⼯作队列模式不需要设置交换机,但queue必填
if (exchange != null) {
throw new RuntimeException("⼯作队列模式⽆须设计交换机");
}
if (queue == null || queue.trim().equals("")) {
throw new RuntimeException("⼯作队列模式必须设置队列名称");
}
if (isDurable == false) {
throw new RuntimeException("⼯作队列模式必须开启数据持久化");
}
} else if (mode == 2) { //路由模式必须设置交换机,但不能设置queue队列
if (exchange == null || exchange.trim().equals("")) {
throw new RuntimeException("路由模式请设置交换机");
}
if (queue != null) {
throw new RuntimeException("路由模式⽆须设置队列名称");
}
}
return true;
//其他各种验证
}
public void sendMessage(String msg) {
System.out.println("正在发送消息:" + msg);
}
public static void main(String[] args) {
RabbitMQClientSample2 client = new RabbitMQClientSample2();
client.setHost("192.168.31.210");
client.setMode(1);
client.setDurable(true);
client.setQueue("queue");
client.validate();
client.sendMessage("Test");
}
}
利⽤SET⽅法虽然灵活,但是存在中间状态,且属性校验时有前后顺序 约束,或者还需要构建额外的校验⽅法 并且SET⽅法破坏了“不可变对象”的密闭性 如何保障灵活组织参数,有可以保证不会存在中间状态以及基本信息不 会对外泄露呢?
建造者模式是⼀个好选择
构建者模式的特点:摆脱超⻓构造⽅法参数的束缚的同时也保护了”不 可变对象“的密闭性
建造者模式的格式如下: ⽬标类的构造⽅法要求传⼊Builder对象 Builder建造者类位于⽬标类内部且⽤static描述 Builder建造者对象提供内置属性与各种set⽅法,注意set⽅法返回 Builder对象本身 Builder建造者类提供build()⽅法实现⽬标类对象的创建
Builder
public class ⽬标类(){
//⽬标类的构造⽅法要求传⼊Builder对象
public ⽬标类(Builder builder){
}
public 返回值 业务⽅法(参数列表){
//doSth
}
//Builder建造者类位于⽬标类内部且⽤static描述
public static class Builder(){
//Builder建造者对象提供内置属性与各种set⽅法,注意set⽅法返回Builder对象本身
private String xxx ;
public Builder setXxx(String xxx) {
this.xxx = xxx;
return this;
}
//Builder建造者类提供build()⽅法实现⽬标类对象的创建
public ⽬标类 build() {
//业务校验
return new ⽬标类(this);
}
}
}
/**
* 建造者模式进⾏优化
*/
public class RabbitMQClient {
private RabbitMQClient(Builder builder) {
}
public void sendMessage(String msg) {
System.out.println("正在发送消息:" + msg);
}
public static class Builder {
private String host = "127.0.0.1";
private int port = 5672;
private int mode;
private String exchange;
private String queue;
private boolean isDurable = true;
private int connectionTimeout = 20;
public Builder setHost(String host) {
this.host = host;
return this;
}
public Builder setPort(int port) {
this.port = port;
return this;
}
public Builder setMode(int mode) {
this.mode = mode;
return this;
}
public Builder setExchange(String exchange) {
this.exchange = exchange;
return this;
}
public Builder setQueue(String queue) {
this.queue = queue;
return this;
}
public Builder setDurable(boolean durable) {
isDurable = durable;
return this;
}
public RabbitMQClient build() {
if (mode == 1) { //⼯作队列模式不需要设置交换机,但queue必填
if (exchange != null) {
throw new RuntimeException("⼯作队列模式⽆须设计交换机");
}
if (queue == null || queue.trim().equals("")) {
throw new RuntimeException("⼯作队列模式必须设置队列名称");
}
if (isDurable == false) {
throw new RuntimeException("⼯作队列模式必须开启数据持久化"
);
}
} else if (mode == 2) { //路由模式必须设置交换机,但不能设置queue队列
if (exchange == null || exchange.trim().equals("")) {
throw new RuntimeException("路由模式请设置交换机");
}
if (queue != null) {
throw new RuntimeException("路由模式⽆须设置队列名称");
}
}
//其他验证
return new RabbitMQClient(this);
}
}
public static void main(String[] args) {
RabbitMQClient client = new RabbitMQClient.Builder()
.setHost("192.168.31.201").setMode(2).setExchange("test-ex
change").build();
client.sendMessage("Test");
}
}
Lombok @Builder 注解
@Builder
public class lombook {
private String host = "127.0.0.1";
private int port = 5672;
private int mode;
private String exchange;
private String queue;
private boolean isDurable = true;
private int connectionTimeout = 20;
public void sendMessage(String message) {
System.out.println("sendMessage: " + message);
}
}
测试:
public static void main(String[] args) {
lombook build = lombook.builder().host("host").port(8080).mode(11).build();
build.sendMessage("hello");
}
展示:
优缺点
优点有以下三点:
- 分离创建与使用。 在建造者模式中, 使用方不必知道你的内部实现算法(步骤)的细节,通过统一方法接口的调用,可以自由组合出不同的对象实例;
- 满足开闭原则。 每一个建造者都相对独立,因此能方便地进行替换或新增,这就大大提升了代码的可扩展性;
- 自由地组合对象的创建过程。由于建造者模式将复杂的创建步骤拆分为单个独立的创建步骤,这不仅使得代码的可读性更高,也使得在创建过程中,使用者可以根据自己的需要灵活创建。
缺点也有以下三点:
- 使用范围有限。 建造者模式所创建的对象一般都需要有很多的共同点,如果对象实例之间的差异性很大,则不适合使用建造者模式;
- 容易引起超大的类。我们都知道一辆汽车内部构造其实很复杂,作为开发者的你其实更关心的是像发动机、轮胎这样具备重用性的组件。一旦过度定制化对象创建的过程步骤,那么随着创建对象新需求的出现或变化,新的创建步骤就会被加进来,这会造成代码量的急剧膨胀,最终形成一个庞大的超大类;
- 增加代码行数。 虽然建造者模式能够提高代码的可阅读性,但也会以增加代码行数来作为代价。
总结
在现实中,经常会遇见很多使用建造者模式的软件,比如:
- Maven、Ant 之类的自动化构建系统;
- Jenkins 等持续集成系统
它们实际上都是使用了建造者模式的具体例子。
建造者模式的主要优点:在于客户端不必知道对象内部组成的细节,将创建与使用进行了很好的解耦,使得可以使用相同的创建过程创建不同的对象,因此符合“开闭原则”,能够极大地提升代码的复用性。同时,因为每一个对象属性的创建步骤都被独立出来,所以还可以更加精细地控制对象的创建过程。 不过,缺点也同样突出。当使用建造者模式创建对象时,需要对象具备更多的共同点才能抽象出更适合的步骤,因此使用范围会受到很大的限制,一旦产品内部开始变得复杂,可能就会导致需要定义很多定制化的建造者类来适应这种变化,从而导致代码变得非常庞大。 应用建造者模式的关键就在于抓住拆分步骤,这是与工厂模式最大的区别所在。如果把建造者模式比作汽车整体组装工厂,那么工厂模式就是汽车配件组装工厂,前者侧重于把对象按照特定步骤组装完整,后者侧重于把组成对象的每一个属性或方法做得更通用后再组装。一定不要以为只要叫“工厂”就是指我们通常认为的统一组装模式。