设计模式建造者模式
Posted Gerrard_Feng
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式建造者模式相关的知识,希望对你有一定的参考价值。
引出问题:
很多人应该遇到这么一种情况,我需要一个对象,但是这个对象比较复杂,有很多属性,你希望在创建对象的同时初始化这些属性。
很自然的,你会想到为这些属性创建对应参数的构造器。
那么问题又来了,如果这些属性,有些时候有,有些时候没有,怎么办呢?
数据模型:
1 @EqualsAndHashCode 2 @Data 3 public class PolicyCommon { 4 5 private String code; 6 private String category; 7 private String name; 8 private String inceptionDate; 9 10 public PolicyCommon() { 11 super(); 12 } 13 14 public PolicyCommon(String code, String category, String name) { 15 super(); 16 this.code = code; 17 this.category = category; 18 this.name = name; 19 } 20 21 }
分析:
以上这个模型,一共拥有4个属性,假定 inceptionDate 属性,在创建对象的时候不需要,其他三个属性都可有可无。
如此一来,初始化的对象可能出现的状态个数为:2^3=8
传统意义上的解决方案:
通过构造器的设计,可以解决这个问题,有两种方案:
- 提供无参构造器,优先创建一个空对象,然后依次调用 set 方法。
- 提供三个参数的构造器,如果某个属性不需要,构造器对应的参数上赋值 null。
以上两种设计,都有各自的问题:
第一种,问题在于,讲构造对象的动作,拆分成多次,导致对象不是一次性构造完成,在中间步骤中出现对象不一致的情况。
第二种,问题在于,从代码上看,属性的意义不明显,如果这是一个不需要任何属性的,如:new PolicyCommon(null, "", null),使用者怎么知道第二个参数到底是什么?
使用建造者模式:
在 《Effective Java》 中,强烈建议在遇到这种复杂的情况时,使用建造者模式来完成创建对象的任务。
建造者模式(Builder Pattern),也称生成器模式,定义如下:
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
Separate the construction of a complex object from its representation so that the same construction process can create different representation.
思想:
使用一个静态内部类 Builder,Builder 中持有外部类中,需要动态生成的属性。
Builder 为每一个属性,提供 set() 方法,但是与通常的set() 方法不同,这些 set() 方法的返回值是 Builder 本身。
Builder 提供一个 build() 方法,返回类型为外部类。
外部类中,仅仅提供一个参数为 Builder 对象的构造器,且将其的权限设置为 private,如此一来,构造器的唯一途径就是 build() 方法。
代码:
1 public class PolicyBuilder { 2 3 private String code; 4 private String category; 5 private String name; 6 private String inceptionDate; 7 8 private PolicyBuilder(Builder builder) { 9 code = builder.code; 10 category = builder.category; 11 name = builder.name; 12 } 13 14 public static class Builder { 15 16 private String code; 17 private String category; 18 private String name; 19 20 public Builder setCode(String code) { 21 this.code = code; 22 return this; 23 } 24 25 public Builder setCategory(String category) { 26 this.category = category; 27 return this; 28 } 29 30 public Builder setName(String name) { 31 this.name = name; 32 return this; 33 } 34 35 public PolicyBuilder build() { 36 return new PolicyBuilder(this); 37 } 38 } 39 40 public String getCode() { 41 return code; 42 } 43 44 public void setCode(String code) { 45 this.code = code; 46 } 47 48 public String getCategory() { 49 return category; 50 } 51 52 public void setCategory(String category) { 53 this.category = category; 54 } 55 56 public String getName() { 57 return name; 58 } 59 60 public void setName(String name) { 61 this.name = name; 62 } 63 64 public String getInceptionDate() { 65 return inceptionDate; 66 } 67 68 public void setInceptionDate(String inceptionDate) { 69 this.inceptionDate = inceptionDate; 70 } 71 72 }
可以看出,建造者模式,相对于之前的传统模式,在初始化的选择,更加自由,最重要的是,它不会使对象处于不同的状态。
但是坏处也有:构造的代码冗长,理解相对困难;而且多一个内部类实例,会增加内存的负担。
使用 Lombok 一键 Builder
细心的朋友,在最初的 PolicyCommon 的数据模型中,会发现两个注解,这是 Lombok 框架的功能。
@Data,为模型的每一个属性,自动生成 get() 和 set() 方法。
@EqualsAndHashCode,为模型生成 equals() 和 hashCode() 方法。
以上两个注解不多介绍,以后有机会详解,这里重点介绍 @Builder 这个注解,它会给一个类自动完成建造者模式。
@Builder 解析:
首先观察 Builder 注解源代码中的 @Target java 元注解:
java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR.
这表明这个注解可以加在:类、构造器、方法,这三个地方,其中我不推荐将这个注解加在方法上,一般都加在类和构造器上。
加在类上的本质其实就是加在方法上,它等同于:为类增加一个 public 的全部参数的构造器,并在这个构造器上加上 @Builder 注解。
加在构造器上,就表示,这些属性是动态的,也就是 Builder 类的内部私有属性。
Lombok 代码:
1 @Data 2 public class PolicyBuilderLombok { 3 4 private String code; 5 private String category; 6 private String name; 7 private String inceptionDate; 8 9 @Builder 10 public PolicyBuilderLombok(String code, String category, String name) { 11 super(); 12 this.code = code; 13 this.category = category; 14 this.name = name; 15 } 16 17 }
Junit 即测试客户端调用:
1 public final class BuilderTest { 2 3 @Test 4 public void test() { 5 PolicyCommon policy1 = new PolicyCommon(); 6 policy1.setName(""); 7 policy1.setCode(""); 8 PolicyCommon policy2 = new PolicyCommon("", null, ""); 9 Assert.assertEquals(policy1, policy2); 10 PolicyBuilder policy3 = new PolicyBuilder.Builder().setName("").setCode("").build(); 11 PolicyBuilderLombok policy4 = new PolicyBuilderLombok.PolicyBuilderLombokBuilder().name("").code("").build(); 12 Assert.assertEquals(policy3.getName(), policy4.getName()); 13 Assert.assertEquals(policy3.getCode(), policy4.getCode()); 14 Assert.assertEquals(policy3.getCategory(), policy4.getCategory()); 15 Assert.assertEquals(policy3.getInceptionDate(), policy4.getInceptionDate()); 16 } 17 18 }
可以看出,除了名称之外,@Builder 注解调用的方式与自己实现建造者模式,并没有什么不同,如果没有一些特殊的要求,Lombok 的 @Builder 注解,可以成为主流的建造者模式的使用方式。
其他应用:
建造者模式在调用的过程中,至始至终,对象只有一个状态。
但是由于调用 Builder 类的方法,存在先后顺序,所以在某些场景下,可以起到自由控制行为执行顺序的功能。
以上是关于设计模式建造者模式的主要内容,如果未能解决你的问题,请参考以下文章