Fun论设计模式之5:建造者模式(Builder Pattern)

  建造者模式在程序设计中经常被运用,下面是建造者模式的概述。

  意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。

  主要解决:主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。

  何时使用:一些基本部件不会变,而其组合经常变化的时候。

  如何解决:将变与不变分离开。

  关键代码:建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。

  应用实例: 1、去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"。 2、JAVA 中的 StringBuilder。

 

  说来复杂,其实要实现一个也很简单。

  跟标准模式相似,不同之处是建造者模式可以直接用本身的复杂组合。

  Java web系统多数都有MySQL表转实体类的组件,直接把生成的实体类的setter返回类型改成实体类本身,返回对象改成本身(this),一个属性也是组合,也能做成一个类builder,跟builder类似的地方是这个对象也可以链式调用改属性的函数;不同的是这个User本身不是单独的builder,且不是延迟build(构造对象)。

 1 public class User {
 2 
 3     private Integer id;
 4 
 5     private String password;
 6 
 7     private String username;
 8 
 9     private Integer userIdentity;
10 
11     private String userMobile;
12     
13     private String classId;
14 
15     /**
16      * 班级ID
17      */
18     @Column(name = "`class_id`")
19     @ApiModelProperty(value = "班级ID(id1,id2,id3...)",example = "班级ID(id1,id2,id3...)",required = false)
20     private String classId;
21 
22     public Integer getId() {
23         return id;
24     }
25 
26     public User setId(Integer id) {
27         this.id = id;
28         return this;
29     }
30 
31     public String getPassword() {
32         return password;
33     }
34 
35     public User setPassword(String password) {
36         this.password = password;
37         return this;
38     }
39 
40     public String getUsername() {
41         return username;
42     }
43 
44     public User setUsername(String username) {
45         this.username = username;
46         return this;
47     }
48 
49     public Integer getUserIdentity() {
50         return userIdentity;
51     }
52 
53     public User setUserIdentity(Integer userIdentity) {
54         this.userIdentity = userIdentity;
55         return this;
56     }
57 
58     public String getUserMobile() {
59         return userMobile;
60     }
61     
62     public User setUserMobile(String userMobile) {
63         this.userMobile = userMobile;
64         return this;
65     }
66 
67     public String getClassId() {
68         return classId;
69     }
70     
71     public User setClassId(String classId) {
72         this.classId = classId;
73         return this;
74     }
75 }
builder化的User实体类

  (当然这里不推荐用这种方式,一般用withXXX这种函数名格式,setXXX用这个不符合规范)

  

  当然这么做也有问题。如果这个User不是单纯在本地程序中使用,还需要请求外部返回数据(这里builder化的实体类也可以与MySQL交互),并且返回实体类的数据结构与请求的数据结构不同(很多rest API的请求数据结构就是这样),请求的builder和响应的实体类也要区分开。User的builder就要这样写:

 1 public class UserBuilder {
 2 
 3     private Integer id;
 4 
 5     private String password;
 6 
 7     private String username;
 8 
 9     private Integer userIdentity;
10 
11     private String userMobile;
12     
13     private String classId;
14 
15     public User build(){
16         User user = new User();
17         user.id = id;
18         user.password = password;
19         user.username = username;
20         user.userIdentity = userIdentity;
21         user.userMobile = userMobile;
22         user.classId = classId;
23         return user;
24     }
25     
26     /**
27      * 班级ID
28      */
29     @Column(name = "`class_id`")
30     @ApiModelProperty(value = "班级ID(id1,id2,id3...)",example = "班级ID(id1,id2,id3...)",required = false)
31     private String classId;
32 
33     public Integer getId() {
34         return id;
35     }
36 
37     public User withId(Integer id) {
38         this.id = id;
39         return this;
40     }
41 
42     public String getPassword() {
43         return password;
44     }
45 
46     public User withPassword(String password) {
47         this.password = password;
48         return this;
49     }
50 
51     public String getUsername() {
52         return username;
53     }
54 
55     public User withUsername(String username) {
56         this.username = username;
57         return this;
58     }
59 
60     public Integer getUserIdentity() {
61         return userIdentity;
62     }
63 
64     public User withUserIdentity(Integer userIdentity) {
65         this.userIdentity = userIdentity;
66         return this;
67     }
68 
69     public String getUserMobile() {
70         return userMobile;
71     }
72     
73     public User withUserMobile(String userMobile) {
74         this.userMobile = userMobile;
75         return this;
76     }
77 
78     public String getClassId() {
79         return classId;
80     }
81     
82     public User withClassId(String classId) {
83         this.classId = classId;
84         return this;
85     }
86 }
真正的builder

  对应的User就可以按照返回的数据结构定制了。

  

  还有一种情况,请求的数据结构比较复杂,某些属性下还有字典和数组,这种情况类似于汽车的生产,里面有些部件不是标准的,一定要预先搭建好(对应于某些属性下的字典),有些组件还不止一个(对应于某些属性下的数组),有些组件如果要修改,与之相关的组件也要进行改动(对象内属性关联修改),那时就需要给类设定一次设置好相关属性的函数。

  像连接kubernetes创建副本控制器(replicationController)的builder,就使用了批量设置复杂对象的函数。

  创建副本控制器的builder:

 1 package io.kubernetes.client.models;
 2 
 3 import io.kubernetes.client.fluent.*;
 4 
 5 public class V1ReplicationControllerBuilder extends V1ReplicationControllerFluentImpl<V1ReplicationControllerBuilder> implements VisitableBuilder<V1ReplicationController, V1ReplicationControllerBuilder>
 6 {
 7     V1ReplicationControllerFluent<?> fluent;
 8     Boolean validationEnabled;
 9     
10     public V1ReplicationControllerBuilder() {
11         this(Boolean.valueOf(true));
12     }
13     
14     public V1ReplicationControllerBuilder(final Boolean validationEnabled) {
15         this(new V1ReplicationController(), validationEnabled);
16     }
17     
18     public V1ReplicationControllerBuilder(final V1ReplicationControllerFluent<?> fluent) {
19         this(fluent, Boolean.valueOf(true));
20     }
21     
22     public V1ReplicationControllerBuilder(final V1ReplicationControllerFluent<?> fluent, final Boolean validationEnabled) {
23         this(fluent, new V1ReplicationController(), validationEnabled);
24     }
25     
26     public V1ReplicationControllerBuilder(final V1ReplicationControllerFluent<?> fluent, final V1ReplicationController instance) {
27         this(fluent, instance, true);
28     }
29     
30     public V1ReplicationControllerBuilder(final V1ReplicationControllerFluent<?> fluent, final V1ReplicationController instance, final Boolean validationEnabled) {
31         (this.fluent = fluent).withApiVersion(instance.getApiVersion());
32         fluent.withKind(instance.getKind());
33         fluent.withMetadata(instance.getMetadata());
34         fluent.withSpec(instance.getSpec());
35         fluent.withStatus(instance.getStatus());
36         this.validationEnabled = validationEnabled;
37     }
38     
39     public V1ReplicationControllerBuilder(final V1ReplicationController instance) {
40         this(instance, Boolean.valueOf(true));
41     }
42     
43     public V1ReplicationControllerBuilder(final V1ReplicationController instance, final Boolean validationEnabled) {
44         ((V1ReplicationControllerFluentImpl<V1ReplicationControllerFluent>)(this.fluent = this)).withApiVersion(instance.getApiVersion());
45         this.withKind(instance.getKind());
46         this.withMetadata(instance.getMetadata());
47         this.withSpec(instance.getSpec());
48         this.withStatus(instance.getStatus());
49         this.validationEnabled = validationEnabled;
50     }
51     
52     @Override
53     public V1ReplicationController build() {
54         final V1ReplicationController buildable = new V1ReplicationController();
55         buildable.setApiVersion(this.fluent.getApiVersion());
56         buildable.setKind(this.fluent.getKind());
57         buildable.setMetadata(this.fluent.getMetadata());
58         buildable.setSpec(this.fluent.getSpec());
59         buildable.setStatus(this.fluent.getStatus());
60         return buildable;
61     }
62     
63     @Override
64     public boolean equals(final Object o) {
65         if (this == o) {
66             return true;
67         }
68         if (o == null || this.getClass() != o.getClass()) {
69             return false;
70         }
71         if (!super.equals(o)) {
72             return false;
73         }
74         final V1ReplicationControllerBuilder that = (V1ReplicationControllerBuilder)o;
75         Label_0088: {
76             if (this.fluent != null && this.fluent != this) {
77                 if (this.fluent.equals(that.fluent)) {
78                     break Label_0088;
79                 }
80             }
81             else if (that.fluent == null || this.fluent == this) {
82                 break Label_0088;
83             }
84             return false;
85         }
86         if (this.validationEnabled != null) {
87             if (this.validationEnabled.equals(that.validationEnabled)) {
88                 return true;
89             }
90         }
91         else if (that.validationEnabled == null) {
92             return true;
93         }
94         return false;
95     }
96 }
ReplicationControllerBuilder

  这里只声明了构造函数和build函数,属性的变更是继承ReplicationController的:

  1 package io.kubernetes.client.models;
  2 
  3 import com.google.gson.annotations.*;
  4 import io.swagger.annotations.*;
  5 import java.util.*;
  6 
  7 @ApiModel(description = "ReplicationController represents the configuration of a replication controller.")
  8 public class V1ReplicationController
  9 {
 10     @SerializedName("apiVersion")
 11     private String apiVersion;
 12     @SerializedName("kind")
 13     private String kind;
 14     @SerializedName("metadata")
 15     private V1ObjectMeta metadata;
 16     @SerializedName("spec")
 17     private V1ReplicationControllerSpec spec;
 18     @SerializedName("status")
 19     private V1ReplicationControllerStatus status;
 20     
 21     public V1ReplicationController() {
 22         this.apiVersion = null;
 23         this.kind = null;
 24         this.metadata = null;
 25         this.spec = null;
 26         this.status = null;
 27     }
 28     
 29     public V1ReplicationController apiVersion(final String apiVersion) {
 30         this.apiVersion = apiVersion;
 31         return this;
 32     }
 33     
 34     @ApiModelProperty("APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources")
 35     public String getApiVersion() {
 36         return this.apiVersion;
 37     }
 38     
 39     public void setApiVersion(final String apiVersion) {
 40         this.apiVersion = apiVersion;
 41     }
 42     
 43     public V1ReplicationController kind(final String kind) {
 44         this.kind = kind;
 45         return this;
 46     }
 47     
 48     @ApiModelProperty("Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds")
 49     public String getKind() {
 50         return this.kind;
 51     }
 52     
 53     public void setKind(final String kind) {
 54         this.kind = kind;
 55     }
 56     
 57     public V1ReplicationController metadata(final V1ObjectMeta metadata) {
 58         this.metadata = metadata;
 59         return this;
 60     }
 61     
 62     @ApiModelProperty("If the Labels of a ReplicationController are empty, they are defaulted to be the same as the Pod(s) that the replication controller manages. Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata")
 63     public V1ObjectMeta getMetadata() {
 64         return this.metadata;
 65     }
 66     
 67     public void setMetadata(final V1ObjectMeta metadata) {
 68         this.metadata = metadata;
 69     }
 70     
 71     public V1ReplicationController spec(final V1ReplicationControllerSpec spec) {
 72         this.spec = spec;
 73         return this;
 74     }
 75     
 76     @ApiModelProperty("Spec defines the specification of the desired behavior of the replication controller. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status")
 77     public V1ReplicationControllerSpec getSpec() {
 78         return this.spec;
 79     }
 80     
 81     public void setSpec(final V1ReplicationControllerSpec spec) {
 82         this.spec = spec;
 83     }
 84     
 85     public V1ReplicationController status(final V1ReplicationControllerStatus status) {
 86         this.status = status;
 87         return this;
 88     }
 89     
 90     @ApiModelProperty("Status is the most recently observed status of the replication controller. This data may be out of date by some window of time. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status")
 91     public V1ReplicationControllerStatus getStatus() {
 92         return this.status;
 93     }
 94     
 95     public void setStatus(final V1ReplicationControllerStatus status) {
 96         this.status = status;
 97     }
 98     
 99     @Override
100     public boolean equals(final Object o) {
101         if (this == o) {
102             return true;
103         }
104         if (o == null || this.getClass() != o.getClass()) {
105             return false;
106         }
107         final V1ReplicationController v1ReplicationController = (V1ReplicationController)o;
108         return Objects.equals(this.apiVersion, v1ReplicationController.apiVersion) && Objects.equals(this.kind, v1ReplicationController.kind) && Objects.equals(this.metadata, v1ReplicationController.metadata) && Objects.equals(this.spec, v1ReplicationController.spec) && Objects.equals(this.status, v1ReplicationController.status);
109     }
110     
111     @Override
112     public int hashCode() {
113         return Objects.hash(this.apiVersion, this.kind, this.metadata, this.spec, this.status);
114     }
115     
116     @Override
117     public String toString() {
118         final StringBuilder sb = new StringBuilder();
119         sb.append("class V1ReplicationController {\n");
120         sb.append("    apiVersion: ").append(this.toIndentedString(this.apiVersion)).append("\n");
121         sb.append("    kind: ").append(this.toIndentedString(this.kind)).append("\n");
122         sb.append("    metadata: ").append(this.toIndentedString(this.metadata)).append("\n");
123         sb.append("    spec: ").append(this.toIndentedString(this.spec)).append("\n");
124         sb.append("    status: ").append(this.toIndentedString(this.status)).append("\n");
125         sb.append("}");
126         return sb.toString();
127     }
128     
129     private String toIndentedString(final Object o) {
130         if (o == null) {
131             return "null";
132         }
133         return o.toString().replace("\n", "\n    ");
134     }
135 }
ReplicationController

  本例子中,副本控制器包含了以下属性:

Kind
ApiVersion
Metadata
--labelMap
--name
Spec
--Replicas
--labelMap
--template
----Metadata
------labelMap
--------labelSelector
------name
----podSpec
------containers
------volumes

  里面有好些属性是重复的,如果不用建造者模式控制会造成某些地方的属性不一致;并且,创建副本控制器的参数多且杂,如果全部平摊成单独属性,改变某些组属性要一行行改,很繁琐,且容易出错。使用建造者模式,像上面的标签集就不用一个个传,对于批量创建某些标签集类似的容器的场合很方便。

  这样既可实现比较简单的builder,又无需重复编写代码,复用对应的实体类函数。

  优点: 1、建造者独立,易扩展。 2、便于控制细节风险。

  缺点: 1、产品必须有共同点,范围有限制。(像本例的副本控制器,换了品类就得推到重来) 2、如内部变化复杂,会有很多的建造类。(还好本例只有一种副本控制器)

  使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。

  注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。

posted @ 2019-10-03 11:54  DGUT_FLY  阅读(241)  评论(0编辑  收藏  举报