建造者模式
什么是建造者模式?
建造者模式是一种创建型设计模式,也叫生成器模式。封装一个复杂对象构造过程,将复杂对象的创建过程拆分成多个简单对象的创建过程,再进行组合构建出复杂对象,用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。
比如建造一个房子,他包含了地基、主体结构、屋顶、内部装修,对于买房者来说,并不需要关注这些环节的建造过程,
你只负责掏钱就能得到房子,而开发商(建造者模式)就是负责将这些部分进行组合后将完整房子给客户。
建造者模式构成
- 产品类(Product)——>房子:要创建的复杂对象 (包含多个组成部件)
- 抽象建造者类(Builder)——>设计师:这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的部件对象的创建。
- 具体建造者类(ConcreteBuilder)——>开发商:实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供一个方法,返回创建好的产品对象。
- 指挥者类(Director)——>项目经理:调用具体建造者来创建复杂对象的各个部分
优缺点
- 优点
- 更好地控制过程:可以一步步构建对象,特别适合构建步骤复杂的对象。
- 易于扩展:不同的建造者可以构建出不同的对象。(150平的房子构建者,120平的房子构建者,90平的房子构建者)
- 更清晰的代码结构:将复杂对象的创建过程封装在建造者中,代码更易于理解和维护。
- 缺点
- 代码复杂度增加:需要创建多个类和接口,增加了代码量。
- 对象的创建过程和表示分离:对于简单对象,使用建造者模式可能显得过于复杂。
与工厂模式的区别
- 工厂模式:主要关注创建单个产品,而建造者模式则关注创建复杂产品的各个部分。
- 工厂方法模式:通过子类来创建产品。
- 抽象工厂模式:创建一系列相关或依赖的对象。
- 建造者模式:将产品的创建过程分解成多个步骤,允许构建复杂对象
建造者模式的实现
现在我们使用建造者模式来完成 150平大户型,120平中等户型,90平小户型的房子建造,房子都由
- 地基
- 主体结构
- 屋顶
- 内部装修
这4个部分构成
UML 类图如下:
- 定义产品类(Product)——>房子(House)
import lombok.Data;
/**
* @Author: javafa
* @Date: 2024/7/15 14:23
* @Description: 产品类,定义要生产一个房子所需要的组成部分
*/
@Data
public class House {
private String foundation; // 地基
private String structure; // 结构
private String roof; // 屋顶
private String interior; // 内部装修
}
- 抽象建造者类(Builder)——> 设计师(HouseBuilder)
/**
* @Author: javafa
* @Date: 2024/7/15 14:32
* @Description: 抽象建造者(Builder)定义建造房子各个部分的抽象方法,就好比设计师把房子各个部分的图纸画好
*/
public abstract class HouseBuilder {
protected House house = new House();
//地基部分的建造
public abstract void buildFoundation();
//主体结构部分的建造
public abstract void buildStructure();
//房顶部分的建造
public abstract void buildRoof();
//内部装修部分的建造
public abstract void buildInterior();
//建造一套完整的房子
public abstract House createHouse();
}
- 具体建造者类(ConcreteBuilder)——> 开发商(LargeSizeHouse)、(MediumSizeHouse)、(MediumSizeHouse)
- LargeSizeHouse 150平大户型
/**
* @Author: javafa
* @Date: 2024/7/15 14:38
* @Description: 大户型房子(150平)建造者
*/
public class LargeSizeHouse extends HouseBuilder{
@Override
public void buildFoundation() {
house.setFoundation("150平 不返潮的好地基建造完成!");
}
@Override
public void buildStructure() {
house.setStructure("150平 18层的主体结构建造完成!");
}
@Override
public void buildRoof() {
house.setRoof("150平 带帽檐的房顶建造完成!");
}
@Override
public void buildInterior() {
house.setInterior("150平 零甲醛的豪华装修建造完成!");
}
@Override
public House createHouse() {
return house;
}
}
- 120平中等户型
/**
* @Author: javafa
* @Description: 中等型房子(120平)建造者
* @Date: 2024/7/15 14:44
**/
public class MediumSizeHouse extends HouseBuilder{
@Override
public void buildFoundation() {
house.setFoundation("120平 不漏水的好地基建造完成!");
}
@Override
public void buildStructure() {
house.setStructure("120平 26层的主体结构建造完成!");
}
@Override
public void buildRoof() {
house.setRoof("120平 有太阳能的房顶建造完成!");
}
@Override
public void buildInterior() {
house.setInterior("120平 拎包入住的装修建造完成!");
}
@Override
public House createHouse() {
return house;
}
}
- 90平小户型
/**
* @Author: javafa
* @Description: 小户型房子(90平)建造者
* @Date: 2024/7/15 14:44
**/
public class SmallSizeHouse extends HouseBuilder{
@Override
public void buildFoundation() {
house.setFoundation("90平 不掉渣的好地基建造完成!");
}
@Override
public void buildStructure() {
house.setStructure("90平 31层的主体结构建造完成!");
}
@Override
public void buildRoof() {
house.setRoof("90平 遮风挡雨的房顶建造完成!");
}
@Override
public void buildInterior() {
house.setInterior("90平 3天就能搞定的装修建造完成!");
}
@Override
public House createHouse() {
return house;
}
}
- 指挥者类(Director)——>Director(项目经理)
/**
* @Author: javafa
* @Date: 2024/7/15 15:26
* @Description: 指挥者(项目经理)
*/
public class Director {
//定义抽象建造者,指挥者根据具体的建造者来创建房子
private HouseBuilder houseBuilder;
public Director(HouseBuilder builder){
this.houseBuilder = builder;
}
/**
* @Author: javafa
* @Description: 指挥者(项目经理) 根据具体的房子构建者(开发商),来建造房子
* @Date: 2024/7/15 15:32
* @Param:
* @return: com.fivemillion.algorithm.designpatterns.builder.House
**/
public House createHouse(){
houseBuilder.buildFoundation();
houseBuilder.buildStructure();
houseBuilder.buildRoof();
houseBuilder.buildInterior();
return houseBuilder.createHouse();
}
}
- 建造者的调用
import com.fivemillion.algorithm.designpatterns.builder.*;
import org.junit.jupiter.api.Test;
/**
* @Author: javafa
* @Date: 2024/7/15 11:15
* @Description: 建造者的调用示例
*/
public class BuilderTest {
@Test
public void builderHouseTest(){
//建造150平的大房子
LargeSizeHouse largeSizeHouse = new LargeSizeHouse();
Director largeDirector = new Director(largeSizeHouse);
House largeHouse = largeDirector.createHouse();
System.out.println(largeHouse.toString());
//建造120平的中等户型房子
MediumSizeHouse mediumSizeHouse = new MediumSizeHouse();
Director mediumDirector = new Director(mediumSizeHouse);
House mediumHouse = mediumDirector.createHouse();
System.out.println(mediumHouse.toString());
//建造90平的小户型房子
SmallSizeHouse smallSizeHouse = new SmallSizeHouse();
Director smallDirector = new Director(smallSizeHouse);
House smallHouse = smallDirector.createHouse();
System.out.println(smallHouse.toString());
}
}
- 运行结果如图:
使用 @Builder 注解实现建造者模式
@Builder 是 Lombok 库提供的一个注解,用于自动生成建造者模式(Builder Pattern)所需的代码。
可以根据其在类上的不同位置和配置产生不同的行为,下面我们使用Person人员信息来举例。
引入lombok
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
</dependency>
- 在类级别使用 @Builder
当 @Builder 注解应用于类级别时,它会为该类生成一个构建器(Builder)类。这个构建器类会包含对应于原类所有非-final成员变量的setter方法,
以及一个 build() 方法来构造原类的对象。
import lombok.Builder;
import lombok.ToString;
/**
* @Author: javafa
* @Date: 2024/7/15 17:48
* @Description: 人员类
*/
@Builder
@ToString
public class Person {
//姓名
private String name;
//年龄
private int age;
//家庭住址
private String address;
}
- 代码等效于
import lombok.ToString;
/**
* @Author: javafa
* @Date: 2024/7/15 17:48
* @Description: 人员类- @Builder注解在类中使用的等价代码
*/
@ToString
public class PersonEq {
//姓名
private final String name;
//年龄
private final int age;
//家庭住址
private final String address;
private PersonEq(PersonBuilder builder) {
this.name = builder.name;
this.age = builder.age;
this.address = builder.address;
}
public static PersonBuilder builder() {
return new PersonBuilder();
}
public static class PersonBuilder {
private String name;
private int age;
private String address;
public PersonBuilder name(String name) {
this.name = name;
return this;
}
public PersonBuilder age(int age) {
this.age = age;
return this;
}
public PersonBuilder address(String address) {
this.address = address;
return this;
}
public PersonEq build() {
return new PersonEq(this);
}
}
}
- 调用示例
@Test
public void usePersonTest(){
//@Builder注解在类中使用建造者模式
Person person = Person.builder()
.name("张三")
.age(12)
.address("苏州市狮子园林")
.build();
//将person转换成字符串输出
System.out.println(person.toString());//输出Person(name=张三, age=12, occupation=科学家)
}
@Test
public void usePersonEqTest(){
//@Builder注解在类中使用的等价代码的建造者模式
PersonEq person = PersonEq.builder()
.name("张三")
.age(12)
.address("苏州市狮子园林")
.build();
//将person转换成字符串输出
System.out.println(person.toString());//输出Person(name=张三, age=12, occupation=科学家)
}
- 使用在构造方法上
将 @Builder 注解使用在构造方法上时,Lombok 会为该构造方法生成一个静态的 Builder 类。\
import lombok.Builder;
import lombok.ToString;
/**
* @Author: javafa
* @Date: 2024/7/15 17:48
* @Description: 人员类-@Builder注解在构造方法中使用
*/
@ToString
public class PersonConstructMethod {
//姓名
private String name;
//年龄
private int age;
//家庭住址
private String address;
// 在构造方法中添加Builder注解
@Builder
public PersonConstructMethod(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
}
- 代码等效于
import lombok.ToString;
/**
* @Author: javafa
* @Date: 2024/7/16 10:39
* @Description: 人员类-@Builder注解在构造方法中使用的等价代码
*/
@ToString
public class PersonConstructMethodEq {
//姓名
private String name;
//年龄
private int age;
//家庭住址
private String address;
public PersonConstructMethodEq(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
public static PersonBuilder builder() {
return new PersonBuilder();
}
public static class PersonBuilder {
private String name;
private int age;
private String address;
public PersonBuilder name(String name) {
this.name = name;
return this;
}
public PersonBuilder age(int age) {
this.age = age;
return this;
}
public PersonBuilder address(String address) {
this.address = address;
return this;
}
public PersonConstructMethodEq build() {
return new PersonConstructMethodEq(this.name, this.age, this.address);
}
}
}
- 调用示例
@Test
public void usePersonConstructMethodTest(){
//使用LookBook的@Builder 在构造方法上添加注解的建造者模式
PersonConstructMethod person = PersonConstructMethod.builder()
.name("张三")
.age(12)
.address("苏州市狮子园林")
.build();
//将person转换成字符串输出
System.out.println(person.toString());//输出Person(name=张三, age=12, occupation=科学家)
}
@Test
public void usePersonConstructMethodEqTest(){
//使用LookBook的@Builder 在构造方法上添加注解的等价代码建造者模式
PersonConstructMethodEq person = PersonConstructMethodEq.builder()
.name("张三")
.age(12)
.address("苏州市狮子园林")
.build();
//将person转换成字符串输出
System.out.println(person.toString());//输出Person(name=张三, age=12, occupation=科学家)
}
- 使用在普通方法上
将 @Builder 注解使用在方法上时,Lombok 会为该方法生成一个静态的 Builder 类,并且返回类型为该方法的返回类型。
import lombok.Builder;
import lombok.ToString;
/**
* @Author: javafa
* @Date: 2024/7/15 17:48
* @Description: 人员类-@Builder注解在普通方法中使用
*/
@ToString
public class PersonCommonMethod {
//姓名
private String name;
//年龄
private int age;
//家庭住址
private String address;
// 在普通方法中添加Builder注解
@Builder
public static PersonCommonMethod create(String name, int age, String address) {
PersonCommonMethod person = new PersonCommonMethod();
person.name = name;
person.age = age;
person.address = address;
return person;
}
}
- 代码等效于
import lombok.ToString;
/**
* @Author: javafa
* @Date: 2024/7/16 10:39
* @Description: 人员类-@Builder注解在普通方法中使用的等价代码
*/
@ToString
public class PersonCommonMethodEq {
//姓名
private String name;
//年龄
private int age;
//家庭住址
private String address;
private PersonCommonMethodEq() {}
private PersonCommonMethodEq(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
public static PersonCommonMethodEq create(String name, int age, String address) {
PersonCommonMethodEq person = new PersonCommonMethodEq();
person.name = name;
person.age = age;
person.address = address;
return person;
}
public static PersonBuilder create() {
return new PersonBuilder();
}
public static class PersonBuilder {
private String name;
private int age;
private String address;
public PersonBuilder name(String name) {
this.name = name;
return this;
}
public PersonBuilder age(int age) {
this.age = age;
return this;
}
public PersonBuilder address(String address) {
this.address = address;
return this;
}
public PersonCommonMethodEq build() {
return new PersonCommonMethodEq(this.name, this.age, this.address);
}
}
}
- 调用示例
@Test
public void usePersonCommonMethodTest(){
//使用LookBook的@Builder 在普通法上添加注解的建造者模式
PersonCommonMethod person = PersonCommonMethod.builder()
.name("张三")
.age(12)
.address("苏州市狮子园林")
.build();
//将person转换成字符串输出
System.out.println(person.toString());//输出Person(name=张三, age=12, occupation=科学家)
}
@Test
public void usePersonCommonMethodEqTest(){
//使用LookBook的@Builder 在普通法上添加注解的等效代码的建造者模式
PersonCommonMethodEq person = PersonCommonMethodEq.create()
.name("张三")
.age(12)
.address("苏州市狮子园林")
.build();
//将person转换成字符串输出
System.out.println(person.toString());//输出Person(name=张三, age=12, occupation=科学家)
}
- 使用在字段上
在说明使用在字段之前,先看下 @Builder带来的问题
使用@Builder 作用在类上时,当为非final修饰的字段赋默认值时,会丢失默认值,看一个示例
import lombok.Builder;
import lombok.ToString;
/**
* @Author: javafa
* @Date: 2024/7/15 17:48
* @Description: 人员类 - @Builder带来的默认字段值丢失
*/
@Builder
@ToString
public class PersonField {
//姓名
private String name;
//年龄 为年龄赋默认值 30
private int age = 30;
//家庭住址
private String address;
}
- 调用测试
- 通过调用测试发现,实体类PersonField中的 age 默认值30 丢失
@Test
public void usePersonFieldTest(){
PersonField person = PersonField.builder()
.name("张三")
.address("苏州市狮子园林")
.build();
System.out.println(person.toString());//输出 PersonField(name=张三, age=0, address=苏州市狮子园林)
}
- 解决方案
- 使用final 修饰
@Builder @ToString public class PersonField { //姓名 private String name; //年龄 为年龄赋默认值 30,使用final修饰保证属性值不可变 private final int age = 30; //家庭住址 private String address; }
- 使用 @Builder.Default 修饰
@Builder @ToString public class PersonField { //姓名 private String name; //年龄 为年龄赋默认值 30,使用@Builder.Default修饰保证属性值不可变 @Builder.Default private int age = 30; //家庭住址 private String address; }
@Builder 的配置项
- builderMethodName:自定义构建器方法的名称。
- buildMethodName:自定义生成最终对象的方法名称。
- toBuilder:生成一个可以从当前对象生成新构建器的 toBuilder 方法。
- setterPrefix:为所有生成的 setter 方法添加前缀。
- builderClassName:自定义生成的构建器类的名称。
- @Builder配置修改
import lombok.Builder;
import lombok.ToString;
/**
* @Author: javafa
* @Date: 2024/7/15 17:48
* @Description: 人员类 - @Builder配置项
*/
@Builder(builderMethodName = "creator",
buildMethodName = "create",
toBuilder = true,
setterPrefix = "with",
builderClassName = "CustomBuilder")
@ToString
public class PersonAttributeUp {
//姓名
private String name;
//年龄
private int age;
//家庭住址
private String address;
}
- 调用示例及说明
@Test
public void usePersonAttributeUpTest(){
PersonAttributeUp person = PersonAttributeUp.creator() // 由于修改配置 builderMethodName = "creator",将生成构建器的静态方法命名为 creator,而不是默认的 builder。
.withName("张三")//由于修改配置setterPrefix = "with":为所有生成的 setter 方法添加 with 前缀,使其符合某些命名规范或编码风格。
.withAge(12)//由于修改配置setterPrefix = "with":为所有生成的 setter 方法添加 with 前缀,使其符合某些命名规范或编码风格。
.withAddress("苏州市狮子园林")//由于修改配置setterPrefix = "with":为所有生成的 setter 方法添加 with 前缀,使其符合某些命名规范或编码风格。
.create();//由于修改配置 buildMethodName = "create":将生成最终对象的方法命名为 create,而不是默认的 build。
System.out.println(person.toString());
PersonAttributeUp personNew = person.toBuilder() // 由于修改配置toBuilder = true:可以从现有对象创建一个新的构建器,拷贝生成新的对象
.withName("张三")
.withAge(12)
.withAddress("苏州市狮子园林")
.create();
System.out.println(person == personNew); //输出false
}