设计模式-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;
    }
}

 

 




相关推荐

策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。 设计原则:

观察者模式:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。 设计原则:</st

装饰者模式:动态将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。 设计原则:类应该对扩展

设计原则:要依赖抽象,不要依赖具体类。 这个原则听起来很像是“针对接口编程,不针对实现编程”,然而这里更强调“抽象”,不能让高层组件依赖底层组件,而且,不管高层或低层组件,“两者”应该依赖于抽象。 一

一、概述 单件模式:确保一个类只有一个实例,并提供一个全局访问点。对于频繁使用的对象,特别是重量级对象,可以省略创建对象所花费的时间,同时降低GC压力。 采用私有构造器。Spring容器中的Bean默

一、概述 命令模式:将动作的请求者从动作的执行者中解耦。 发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何工作的。 命

一、概述 适配器模式:将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。 以上是对象适配器的图。 以上是类适配器的图。

外观模式:提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。 适配器是包装一个类,而外观模式是包装多个

模板方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。 算法内的步骤不要切割的太细,否则会较没有弹性。 钩子是

迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。把游走的任务放在迭代器上,而不是聚合上。这样简化了聚合的接口和实现,也让责任各