建造者模式在程序设计中经常被运用,下面是建造者模式的概述。
意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
主要解决:主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
何时使用:一些基本部件不会变,而其组合经常变化的时候。
如何解决:将变与不变分离开。
关键代码:建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。
应用实例: 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 }
(当然这里不推荐用这种方式,一般用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 }
对应的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 }
这里只声明了构造函数和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 { "); 120 sb.append(" apiVersion: ").append(this.toIndentedString(this.apiVersion)).append(" "); 121 sb.append(" kind: ").append(this.toIndentedString(this.kind)).append(" "); 122 sb.append(" metadata: ").append(this.toIndentedString(this.metadata)).append(" "); 123 sb.append(" spec: ").append(this.toIndentedString(this.spec)).append(" "); 124 sb.append(" status: ").append(this.toIndentedString(this.status)).append(" "); 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(" ", " "); 134 } 135 }
本例子中,副本控制器包含了以下属性:
Kind ApiVersion Metadata --labelMap --name Spec --Replicas --labelMap --template ----Metadata ------labelMap --------labelSelector ------name ----podSpec ------containers ------volumes
里面有好些属性是重复的,如果不用建造者模式控制会造成某些地方的属性不一致;并且,创建副本控制器的参数多且杂,如果全部平摊成单独属性,改变某些组属性要一行行改,很繁琐,且容易出错。使用建造者模式,像上面的标签集就不用一个个传,对于批量创建某些标签集类似的容器的场合很方便。
这样既可实现比较简单的builder,又无需重复编写代码,复用对应的实体类函数。
优点: 1、建造者独立,易扩展。 2、便于控制细节风险。
缺点: 1、产品必须有共同点,范围有限制。(像本例的副本控制器,换了品类就得推到重来) 2、如内部变化复杂,会有很多的建造类。(还好本例只有一种副本控制器)
使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。
注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。