设计模式-建造者模式的完整代码示例及测试验证

建造者模式

什么是建造者模式?

建造者模式是一种创建型设计模式,也叫生成器模式。封装一个复杂对象构造过程,将复杂对象的创建过程拆分成多个简单对象的创建过程,再进行组合构建出复杂对象,用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。

比如建造一个房子,他包含了地基、主体结构、屋顶、内部装修,对于买房者来说,并不需要关注这些环节的建造过程,
你只负责掏钱就能得到房子,而开发商(建造者模式)就是负责将这些部分进行组合后将完整房子给客户。

建造者模式构成

  1. 产品类(Product)——>房子:要创建的复杂对象 (包含多个组成部件)
  2. 抽象建造者类(Builder)——>设计师:这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的部件对象的创建。
  3. 具体建造者类(ConcreteBuilder)——>开发商:实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供一个方法,返回创建好的产品对象。
  4. 指挥者类(Director)——>项目经理:调用具体建造者来创建复杂对象的各个部分

优缺点

  • 优点
  1. 更好地控制过程:可以一步步构建对象,特别适合构建步骤复杂的对象。
  2. 易于扩展:不同的建造者可以构建出不同的对象。(150平的房子构建者,120平的房子构建者,90平的房子构建者)
  3. 更清晰的代码结构:将复杂对象的创建过程封装在建造者中,代码更易于理解和维护。
  • 缺点
  1. 代码复杂度增加:需要创建多个类和接口,增加了代码量。
  2. 对象的创建过程和表示分离:对于简单对象,使用建造者模式可能显得过于复杂。

与工厂模式的区别

  • 工厂模式:主要关注创建单个产品,而建造者模式则关注创建复杂产品的各个部分。
  • 工厂方法模式:通过子类来创建产品。
  • 抽象工厂模式:创建一系列相关或依赖的对象。
  • 建造者模式:将产品的创建过程分解成多个步骤,允许构建复杂对象

建造者模式的实现

现在我们使用建造者模式来完成 150平大户型,120平中等户型,90平小户型的房子建造,房子都由

  1. 地基
  2. 主体结构
  3. 屋顶
  4. 内部装修

这4个部分构成


UML 类图如下:
在这里插入图片描述

  1. 定义产品类(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;   // 内部装修


}

  1. 抽象建造者类(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();

}
  1. 具体建造者类(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;
    }
}

  1. 指挥者类(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();
        }
}

  1. 建造者的调用

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());
    }
}

  1. 运行结果如图:

在这里插入图片描述


使用 @Builder 注解实现建造者模式

@Builder 是 Lombok 库提供的一个注解,用于自动生成建造者模式(Builder Pattern)所需的代码。
可以根据其在类上的不同位置和配置产生不同的行为,下面我们使用Person人员信息来举例。

引入lombok

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.32</version>
        </dependency>

  1. 在类级别使用 @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=科学家)
}

  1. 使用在构造方法上

将 @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=科学家)
}
  1. 使用在普通方法上

将 @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=科学家)
    }


  1. 使用在字段上

在说明使用在字段之前,先看下 @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;
}

  • 调用测试
  1. 通过调用测试发现,实体类PersonField中的 age 默认值30 丢失

    @Test
    public void usePersonFieldTest(){
        PersonField person = PersonField.builder()
                .name("张三")
                .address("苏州市狮子园林")
                .build();
        System.out.println(person.toString());//输出 PersonField(name=张三, age=0, address=苏州市狮子园林)
    }


  1. 解决方案
    1. 使用final 修饰
    @Builder
    @ToString
    public class PersonField {
      //姓名
      private String name;
      //年龄 为年龄赋默认值 30,使用final修饰保证属性值不可变
      private final int age = 30;
      //家庭住址
      private String address;
    }
    
    
    1. 使用 @Builder.Default 修饰
    @Builder
    @ToString
    public class PersonField {
       //姓名
       private String name;
       //年龄 为年龄赋默认值 30,使用@Builder.Default修饰保证属性值不可变
       @Builder.Default
       private  int age = 30;
       //家庭住址
       private String address;
    }
    
    

@Builder 的配置项

  1. builderMethodName:自定义构建器方法的名称。
  2. buildMethodName:自定义生成最终对象的方法名称。
  3. toBuilder:生成一个可以从当前对象生成新构建器的 toBuilder 方法。
  4. setterPrefix:为所有生成的 setter 方法添加前缀。
  5. 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
    }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FiveMillion

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值