设计模式-Builder模式(建造者模式/生成器模式)
阅读数:97 评论数:0
跳转到新版页面分类
架构学
正文
一、概述
建造者模式(Builder pattern)通过将一个复杂对象的构建过程与它的表现(属性)分离,使得构建的过程可以自由扩展,降低部件与组装过程的耦合,是创建型模式。
建造者模式是一步步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。
二、示例
考虑这样一个场景,假如有一个类(User),里面有很多属性,并且你希望这些类的属性都是不可变的(final),就像下面的代码。
public class User {
private final String firstName; // 必传参数
private final String lastName; // 必传参数
private final int age; // 可选参数
private final String phone; // 可选参数
private final String address; // 可选参数
}
在这个类中,有些参数是必要的,而有些参数是非必要的,就好比在注册用户时,用户的姓和名是必填的,而年龄、手机号和家庭地址等是非必需要。那么问题就来了,如何创建这个类的对象呢?
1、一种方案是使用构造方法。
第一个构造方法只包含两个必需要参数,第二个构造方法中,增加一个可选参数,第三个构造方法中再增加一个可选参数,依次类推,直到构造方法中包含了所有参数。
public User(String firstName, String lastName) {
this(firstName, lastName, 0);
}
public User(String firstName, String lastName, int age) {
this(firstName, lastName, age, "");
}
public User(String firstName, String lastName, int age, String phone) {
this(firstName, lastName, age, phone, "");
}
public User(String firstName, String lastName, int age, String phone, String address) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.phone = phone;
this.address = address;
}
弊端:
(1)一旦参数多了,代码可读性就差,并且难以维护。
(2)对调用者来说麻烦。
2、第二种解决方案,就是设置一个空参数的构造方法,然后为每一个属性设置setters和getters。
public class User {
private String firstName; // 必传参数
private String lastName; // 必传参数
private int age; // 可选参数
private String phone; // 可选参数
private String address; // 可选参数
public User() {
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getAge() {
return age;
}
public String getPhone() {
return phone;
}
public String getAddress() {
return address;
}
}
弊端:
(1)不可变类的所有好处都不复存在。
(2)对象会产生不一致的状态,当你想要传入5个参数的时候,你必须将所有的setxx方法调用完成之后才行,然而一部分的调用者看到这个对象后,以为这个对象已创建完毕,就直接使用了,其实User对象并没有创建完成。
3、现在我们使用Builder模式
public class User {
private final String firstName; // 必传参数
private final String lastName; // 必传参数
private final int age; // 可选参数
private final String phone; // 可选参数
private final String address; // 可选参数
private User(UserBuilder builder) {
this.firstName = builder.firstName;
this.lastName = builder.lastName;
this.age = builder.age;
this.phone = builder.phone;
this.address = builder.address;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getAge() {
return age;
}
public String getPhone() {
return phone;
}
public String getAddress() {
return address;
}
public static class UserBuilder {
private final String firstName;
private final String lastName;
private int age;
private String phone;
private String address;
public UserBuilder(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public UserBuilder age(int age) {
this.age = age;
return this;
}
public UserBuilder phone(String phone) {
this.phone = phone;
return this;
}
public UserBuilder address(String address) {
this.address = address;
return this;
}
public User build() {
return new User(this);
}
}
}
有现个重要的地方需要强调一下:
(1)User类的构造方法是私有的,也就是说调用者不能直接创建User对象。
(2)User类的属性都是不可变的,所有的属性都添加了final修饰符,并且在构造方法中设置了值,并且,对外只提供getters方法。
(3)Builder模式使用了链式调用,可读性更佳。
(4)Builder的内部类构造方法中只接收必传的参数,并且该必传的参数使用final修饰符。
三、继承下的使用
public class BaseMessage {
/**
* 消息类型:text、textcard、
*/
private String msgtype;
/**
* 应用id
*/
private Integer agentid;
/**
* 加密方式。默认不传或传0传2不进行任何解密处理直接发送文本 。传1对整个文本内容将进行base64解密。传3对整个文本内容进行UrlEncode编码
*/
private String touser;
/**
* 部门ID列表,多个接收者用‘|’分隔,最多支持100个。部门id请查询OA接口获取部门id。注:使用该参数会对整个部门的全部人员进行群发,请慎重使用
*/
private String toparty;
/**
* 标签ID列表,多个接收者用‘|’分隔,最多支持100个。当touser为@all时忽略本参数。
*/
private String totag;
/**
* 表示是否是保密消息,0表示否,1表示是,默认0
*/
private Integer safe;
public BaseMessage(String msgtype) {
this.msgtype = msgtype;
}
public BaseMessage() {
}
public <T extends BaseMessageBuilder> BaseMessage(BaseMessageBuilder builder) {
this.msgtype = builder.msgtype;
this.agentid = builder.agentid;
this.touser = builder.touser;
this.toparty = builder.toparty;
this.totag = builder.totag;
this.safe = builder.safe;
}
public String getMsgtype() {
return msgtype;
}
public Integer getAgentid() {
return agentid;
}
public String getTouser() {
return touser;
}
public String getToparty() {
return toparty;
}
public String getTotag() {
return totag;
}
public Integer getSafe() {
return safe;
}
/**
* 在父类中使用泛型 根据调用的子类不同 返回子类对应的Builder
*/
public static class BaseMessageBuilder<T extends BaseMessageBuilder> {
private String msgtype;
private Integer agentid;
private String touser;
private String toparty;
private String totag;
private Integer safe;
public BaseMessageBuilder(String msgtype) {
this.msgtype = msgtype;
}
public T agentid(Integer agentid) {
this.agentid = agentid;
return (T) this;
}
public T touser(String touser) {
this.touser = touser;
return (T) this;
}
public T toparty(String toparty) {
this.toparty = toparty;
return (T) this;
}
public T totag(String totag) {
this.totag = totag;
return (T) this;
}
public T safe(Integer safe) {
this.safe = safe;
return (T) this;
}
}
}
public class WorkWeiXinContentText extends BaseMessage {
private static final String MSGTYPE = "text";
private Text text;
public WorkWeiXinContentText(String content) {
super(MSGTYPE);
this.text = new Text(content);
}
public WorkWeiXinContentText(WorkWeiXinContentTextBuilder builder) {
/**
* 调用的是父类的构造方法 public <T extends BaseMessageBuilder> BaseMessage(BaseMessageBuilder builder)
*/
super(builder);
this.text = builder.text;
}
public static WorkWeiXinContentTextBuilder builder() {
return new WorkWeiXinContentTextBuilder(MSGTYPE);
}
public Text getText() {
return text;
}
/**
* 如果先赋值子类的属性 则返回的是子类的WorkWeiXinContentTextBuilder ,如果先赋值父类的属性 不加泛型的话 返回的是父类的 BaseMessageBuilder 无法转换为子类对象 WorkWeiXinContentText
*/
public static class WorkWeiXinContentTextBuilder extends BaseMessageBuilder<WorkWeiXinContentTextBuilder> {
private Text text;
WorkWeiXinContentTextBuilder(String msgtype) {
super(msgtype);
}
public WorkWeiXinContentTextBuilder text(Text text) {
this.text = text;
return this;
}
public WorkWeiXinContentText build() {
return new WorkWeiXinContentText(this);
}
}
}
四、Lombok的@Builder
1、@Builder可以放在类、构造函数或方法上。
2、放在类上
@Builder
public class User {
private String username;
private String password;
}
// 编译后:
public class User {
private String username;
private String password;
User(String username, String password) {
this.username = username; this.password = password;
}
public static User.UserBuilder builder() {
return new User.UserBuilder();
}
public static class UserBuilder {
private String username;
private String password;
UserBuilder() {}
public User.UserBuilder username(String username) {
this.username = username;
return this;
}
public User.UserBuilder password(String password) {
this.password = password;
return this;
}
public User build() {
return new User(this.username, this.password);
}
public String toString() {
return "User.UserBuilder(username=" + this.username + ", password=" + this.password + ")";
}
}
}
3、集合属性@Singular
在使用@Singular注解注释一个集合字段,lombok会生成两个adder方法,一个用于添加单个元素,另一个用于添加一个集合。
@Target({FIELD, PARAMETER})
@Retention(SOURCE)
public @interface Singular {
// 修改添加集合元素的方法名
String value() default "";
}
@Builder
public class User {
private final Integer id;
private final String zipCode = "215500";
private String username;
private String password;
@Singular
private List<String> hobbies;
}
// 编译后:
public class User {
private final Integer id;
private final String zipCode = "215500";
private String username;
private String password;
private List<String> hobbies;
User(Integer id, String username, String password, List<String> hobbies) {
this.id = id; this.username = username;
this.password = password; this.hobbies = hobbies;
}
public static User.UserBuilder builder() {return new User.UserBuilder();}
public static class UserBuilder {
private Integer id;
private String username;
private String password;
private ArrayList<String> hobbies;
UserBuilder() {}
public User.UserBuilder id(Integer id) { this.id = id; return this; }
public User.UserBuilder username(String username) { this.username = username; return this; }
public User.UserBuilder password(String password) { this.password = password; return this; }
public User.UserBuilder hobby(String hobby) {
if (this.hobbies == null) {
this.hobbies = new ArrayList();
}
this.hobbies.add(hobby);
return this;
}
public User.UserBuilder hobbies(Collection<? extends String> hobbies) {
if (this.hobbies == null) {
this.hobbies = new ArrayList();
}
this.hobbies.addAll(hobbies);
return this;
}
public User.UserBuilder clearHobbies() {
if (this.hobbies != null) {
this.hobbies.clear();
}
return this;
}
public User build() {
List hobbies;
switch(this.hobbies == null ? 0 : this.hobbies.size()) {
case 0:
hobbies = Collections.emptyList();
break;
case 1:
hobbies = Collections.singletonList(this.hobbies.get(0));
break;
default:
hobbies = Collections.unmodifiableList(new ArrayList(this.hobbies));
}
return new User(this.id, this.username, this.password, hobbies);
}
public String toString() {
return "User.UserBuilder(id=" + this.id + ", username=" + this.username + ", password=" + this.password + ", hobbies=" + this.hobbies + ")";
}
}
}
4、@Builder.Default
@Builder
@ToString
public class User {
@Builder.Default
private final String id = UUID.randomUUID().toString();
private String username;
private String password;
@Builder.Default
private long insertTime = System.currentTimeMillis();
}
这样当我们使用这个实体对象时,就不需要对这两个字段进行初始化。
5、@Builder详细配置
@Target({TYPE, METHOD, CONSTRUCTOR})
@Retention(SOURCE)
public @interface Builder {
// 如果@Builder注解在类上,可以使用 @Builder.Default指定初始化表达式
@Target(FIELD)
@Retention(SOURCE)
public @interface Default {}
// 指定实体类中创建 Builder 的方法的名称,默认为: builder (个人觉得没必要修改)
String builderMethodName() default "builder";
// 指定 Builder 中用来构件实体类的方法的名称,默认为:build (个人觉得没必要修改)
String buildMethodName() default "build";
// 指定创建的建造者类的名称,默认为:实体类名+Builder
String builderClassName() default "";
// 使用toBuilder可以实现以一个实例为基础继续创建一个对象。(也就是重用原来对象的值)
boolean toBuilder() default false;
@Target({FIELD, PARAMETER})
@Retention(SOURCE)
public @interface ObtainVia {
// 告诉lombok使用表达式获取值
String field() default "";
// 告诉lombok使用表达式获取值
String method() default "";
boolean isStatic() default false;
}
}
比较常用的是toBuilder,因为我们在对实体对象进行操作时,往往会存在对某些实体对象的某个字段进行二次赋值,这个时候就会用到这一属性。但是,这会创建一个新的对象,而不是原来的对象,原来的对象属性是不可变的,除非你自己想要给这个实体类再添加上@Data
或者@setter
方法。
@Builder(toBuilder = true)
@ToString
public class User {
private String username;
private String password;
}
// 测试类
public class BuilderTest {
public static void main(String[] args) {
User user1 = User.builder()
.password("jdkong")
.username("jdkong")
.build();
System.out.println(user1);
User user2 = user1.toBuilder().username("jdkong2").build();
// 验证user2是否是基于user1的现有属性创建的
System.out.println(user2);
// 验证对象是否是同一对象
System.out.println(user1 == user2);
}
}
// 输出内容
User(username=jdkong, password=jdkong)
User(username=jdkong2, password=jdkong)
false
6、继承下的使用
@Getter
@AllArgsConstructor
public class Parent {
private final String parentName;
private final int parentAge;
}
@Getter
public class Child extends Parent {
private final String childName;
private final int childAge;
@Builder
public Child(String parentName, int parentAge, String childName, int childAge) {
super(parentName, parentAge);
this.childName = childName;
this.childAge = childAge;
}
}