设计模式建造者模式

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 }
PolicyBuilder

 

  可以看出,建造者模式,相对于之前的传统模式,在初始化的选择,更加自由,最重要的是,它不会使对象处于不同的状态。

  但是坏处也有:构造的代码冗长,理解相对困难;而且多一个内部类实例,会增加内存的负担。

 

 

使用 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 }
PolicyBuilderLombok

 

 

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 }
JUnit

 

  可以看出,除了名称之外,@Builder 注解调用的方式与自己实现建造者模式,并没有什么不同,如果没有一些特殊的要求,Lombok 的 @Builder 注解,可以成为主流的建造者模式的使用方式。

 

 

其他应用:

 

  建造者模式在调用的过程中,至始至终,对象只有一个状态。

  但是由于调用 Builder 类的方法,存在先后顺序,所以在某些场景下,可以起到自由控制行为执行顺序的功能。

 

以上是关于设计模式建造者模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之建造者模式

建造者模式(Builder)

设计模式 创建者模式 -- 建造者模式

设计模式从青铜到王者第八篇:创建型模式之建造者模式(BuilderPattern)

Java设计模式-建造者模式

设计模式:学习笔记——建造者模式