建造者模式
什么是建造者模式
建造者模式
又叫创建者模式
,是将一个复杂的对象的构建与它的表示分离
,使得同样的构建过程可以创建不同的表示
。创建者模式隐藏了复杂对象的创建过程
,它把复杂对象的创建过程加以抽象
,通过子类继承
或者重载
的方式,动态的创建具有复合属性的对象
。
主要作用
在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象
。
@Builder注解
为什么要使用Builder
当一个类的成员变量比较多
,同时,大部分的成员变量有默认值
,仅有少量的成员变量需要初始化
时,我们该如何设计这个类?我们需要让该类满足以下两个要求:
方便用户创建复杂的对象(不需要知道实现过程),仅需要初始化必要的成员变量;
代码复用性 & 封装性(将对象构建过程和细节进行封装 & 复用)
例如:有一个类的成员变量很多,复杂
。那么就会有很多种类的构造函数
。这个时候就会需要一种方式能够动态的创建这个类
。
而这个时候这种设计场景就与建造者模式
不谋而合。为此lombok
就推出一款注解 @Builder
@Builder流程分析
编译前代码
@Data
@Builder
public class Student {
String name;
int age;
}
反编译后主要代码
public class Student {
String name;
int age;
Student(final String name, final int age) {
this.name = name;
this.age = age;
}
public static Student.StudentBuilder builder() {
return new Student.StudentBuilder();
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
public void setName(final String name) {
this.name = name;
}
public void setAge(final int age) {
this.age = age;
}
public String toString() {
return "Student(name=" + this.getName() + ", age=" + this.getAge() + ")";
}
public static class StudentBuilder {
private String name;
private int age;
StudentBuilder() {
}
public Student.StudentBuilder name(final String name) {
this.name = name;
return this;
}
public Student.StudentBuilder age(final int age) {
this.age = age;
return this;
}
public Student build() {
return new Student(this.name, this.age);
}
public String toString() {
return "Student.StudentBuilder(name=" + this.name + ", age=" + this.age + ")";
}
}
}
测试代码
@Test
public void testBuilder(){
Student student = Student.builder()
.name("18")
.age(19)
.build();
System.out.println(student.toString());
}
由此可以看出@Builder
下的使用方式主要是通过以下几个方法,实现分步构造
:
public static Student.StudentBuilder builder() {
return new Student.StudentBuilder();
}
静态内部类 StudentBuilder
public static class StudentBuilder {
private String name;
private int age;
StudentBuilder() {
}
public Student.StudentBuilder name(final String name) {
this.name = name;
return this;
}
public Student.StudentBuilder age(final int age) {
this.age = age;
return this;
}
public Student build() {
return new Student(this.name, this.age);
}
}
通过类方法builder()
, 创建一个StudentBuilder()的类
。然后这个类
通过返回自身(this) 的链式编程
可以动态的构造对象
,最后通过build
构造出一个新的Student的对象
,实现了构造过程
与展现最后对象的解耦合
。
而在这个过程中builder()就是抽象建造者
,它决定了由哪个实际的builder类(StudentBuilder)去创建它
。而在完成对创建顺序的控制
,完成创建过程的分离
这个过程的也就是指挥者
的功能。如
Student.builder().name("18").age(19).build();
优缺点
优点
不需些太多的set方法来定义属性内容
写法更优雅
缺点
在整个过程中会多创建个中间类StudentBuilder实现解耦合,多占用了内存。
由于bulider()方法是类方法,所以在进行创建过第一次后,不能对创建后的对象继续进行动态赋值。
额外
@Builder(toBuilder = true)
这个选项允许你将一个实例化好的Card更新字段生成新的Card实例。
public Card.CardBuilder toBuilder() {
return (new Card.CardBuilder()).id(this.id).name(this.name).sex(this.sex);
}
可以清楚的看出来,toBuilder方法是用当前实例的属性构造了一个新的Builder实例。
@Accessors
该注解主要作用是:
当属性字段在生成 getter 和 setter 方法时,做一些相关的设置。
当它可作用于类上时,修饰类中所有字段,当作用于具体字段时,只对该字段有效。
源码
package lombok.experimental;
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface Accessors {
boolean fluent() default false;
boolean chain() default false;
String[] prefix() default {};
}
属性
fluent 属性
不写默认为false
当该值为 true 时,对应字段的 getter 方法前面就没有 get,setter 方法就不会有 set。
chain 属性
不写默认为false
当该值为 true 时,对应字段的 setter 方法调用后,会返回当前对象。
prefix 属性
该属性是一个字符串数组
当该数组有值时,表示忽略字段中对应的前缀,生成对应的 getter 和 setter 方法
比如现在有 xxName 字段
和 yyAge 字段
,xx
和 yy
分别是 name 字段和 age 字段的前缀
那么,我们在生成的 getter 和 setter 方法
如下,它也是带有 xx 和 yy 前缀的
前缀加到 @Accessors 的 prefix 属性值
中,则可以像没有前缀那样,去调用字段的 getter和 setter 方法
@Accessors流程分析
编译前代码
@Data
@Accessors(chain = true)
public class Student {
String name;
int age;
}
反编译后的主要代码
public class Student {
String name;
int age;
public Student() {
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
public Student setName(final String name) {
this.name = name;
return this;
}
public Student setAge(final int age) {
this.age = age;
return this;
}
}
测试代码
@Test
public void testBuilder(){
Student student = new Student().setAge(18).setName("chen");
System.out.println(student.toString());
}
可以看到通过设置chain = true
则开启了链式构造
,在自身赋值的时候,返回自身对象
。避免了多余bean的创建
,以及可以继续通过自身进行链式赋值
。
对比
1 两者都可以实现链式构造
结合
2 如果想要实战方便些
可以使用@Accessors,
如果想要更贴合建造者模式进行解耦合
的话可以使用@Builder
3 @Builder
就是基于建造者模式支持链式操作,但很多时候都是构造失血模式的Bean
或者没有共享变量
,这时候为了链式操作就新建一个builder是不是有点大材小用
@Accessors
就可以解决上述的问题,支持链式操作
,同时减少多余对象的创建
,builder类元信息又可以减少
注意
使用@Builder 第二次链式赋值
如果一定要使用@Builder进行对象的 第二次以后的继续链式赋值 可以结合 toBuild=true 使用
即@Builder(toBuilder = true)
@Test
publicvoid testBuilder(){
Student student = Student.builder().sAge(19).sName("chen").build();
student.toBuilder().sAge(20);
}
@Data和@Builder 一起使用,要加@NoArgsConstructor和@AllArgsConstructor
@Builder
与@Data
共用时, 注意加 @NoArgsConstructor
和 @AllArgsConstructor
注解
因为@Builder生成的全参构造器
, 与@Data
所包含的@RequiredArgsConstructor发生冲突
@Data
和@Builder
一块儿就没有了默认的构造方法
BeanUtils.copyProperties失效
用 @Accessors(chain = true)
注解 时, 由于set方法带返回值
, 导致一些BeanUtils.copyProperties失效
解决方法: 导包时选择这个
import org.springframework.beans.BeanUtils;