建造者模式

2021-08-01   


建造者模式

前言

  最近在项目中发现对于许多对象的创建都使用了lombok的@Builder,即建造者模式来创建。由于之前对此设计模式只是了解的程度,故在此进行简单学习记录。

简介

  建造者模式(生成器模式)是一种创建型设计模式,它使得我们可以分步来创建一个复杂对象,同时通过相同的创建代码根据需要生成不同类型和形式的对象。
  建造者模式建议将构造代码从产品类中抽取出来,并将其放在一个独立的生成器对象中。通过生成器有选择地调用一系列步骤来创建特定的对象。同时,我们可以进一步将用于创建对象的一系列生成器步骤调用抽取为单独的主管类,在其中定义创建步骤的执行顺序。

适用场景

  使用建造者模式可以避免"重叠构造函数"(重载)的出现。假设我们的构造函数中有十个可选参数,那么我们调用该函数会非常不方便;当我们使用重载函数减少参数时,内部仍然需要调用主构造函数并传递一些默认值;随着需求的增加会出现更多的重载函数以便适配不同的参数场景。
  当我们需要创建各种形式的产品,它们的制造过程仅有细节上的差异,此时可使用建造者模式。基本生成器接口中定义了所有可能的制造步骤,具体生成器将实现这些步骤来制造产品。同时主管类可以负责管理制造步骤的顺序。
  当然,我们也可以通过建造者模式来构造组合树或其他复杂对象,分步构造产品。

demo

通用生成器接口

public interface Builder {

    /**
     * 汽车类型
     *
     * @param carType
     */
    void setCarType(String carType);

    /**
     * 引擎
     *
     * @param engine
     */
    void setEngine(Engine engine);

    /**
     * 核载
     *
     * @param number
     */
    void setNuclearLoad(Integer number);

    /**
     * 产地
     *
     * @param place
     */
    void setPlaceOfOrigin(String place);
}

汽车生成器

public class CarBuilder implements Builder {

    private String carType;

    private Engine engine;

    private Integer number;

    private String place;


    @Override
    public void setCarType(String carType) {
        this.carType = carType;
    }

    @Override
    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    @Override
    public void setNuclearLoad(Integer number) {
        this.number = number;
    }

    @Override
    public void setPlaceOfOrigin(String place) {
        this.place = place;
    }

    public Car getResult() {
        return new Car(carType, engine, number, place);
    }

}

汽车手册生成器

public class CarManualBuilder implements Builder {

    private String carType;

    private Engine engine;

    private Integer number;

    private String place;

    @Override
    public void setCarType(String carType) {
        this.carType = carType;
    }

    @Override
    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    @Override
    public void setNuclearLoad(Integer number) {
        this.number = number;
    }

    @Override
    public void setPlaceOfOrigin(String place) {
        this.place = place;
    }

    public Manual getResult() {
        return new Manual(carType, engine, number, place);
    }
}

汽车

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Car {

    private String carType;

    private Engine engine;

    private Integer number;

    private String place;

}

手册

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Manual {

    private String carType;

    private Engine engine;

    private Integer number;

    private String place;

}

引擎-汽车的属性

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Engine {

    private String coolingMethod;

    private String ignitionMode;

}

主管

public class Director {

    void constructBMWCar(Builder builder) {
        builder.setCarType("BMW");
        builder.setEngine(new Engine("water", "compression"));
        builder.setNuclearLoad(4);
        builder.setPlaceOfOrigin("china");
    }

    void constructSUVCar(Builder builder){
        builder.setCarType("SUV");
        builder.setEngine(new Engine("wind", "spark"));
        builder.setNuclearLoad(7);
        builder.setPlaceOfOrigin("china");
    }
}

测试

public class CarDemo {

    public static void main(String[] args) {
        Director director = new Director();

        CarBuilder carBuilder = new CarBuilder();
        director.constructBMWCar(carBuilder);
        Car car = carBuilder.getResult();
        System.out.println(car.getCarType());

        CarManualBuilder carManualBuilder = new CarManualBuilder();
        director.constructSUVCar(carManualBuilder);
        Manual manual = carManualBuilder.getResult();
        System.out.println(manual.getEngine().getCoolingMethod());
    }
}

Demo中,通过生成器我们可以生成不同型号的汽车,同时可以使用相同的生成过程制造不同类型的产品:汽车手册。主管类控制构造顺序(步骤),它仅仅与通用接口进行交互,通过传递不同的生成器,最终从生成器中获得对象(最终的产品类型)。

其他

lombok的@Builder究竟是怎么实现的建造者模式,直接看下编译后的文件吧

    public static com.example.demo.builder.Car.CarBuilder builder() {
        return new com.example.demo.builder.Car.CarBuilder();
    }

可以看到被@Builder标记的类提供了一个指向匿名内部类(CarBuilder)的builder方法。

public class Car$CarBuilder {
    private String carType;
    private Engine engine;
    private Integer number;
    private String place;

    Car$CarBuilder() {
    }

    public Car$CarBuilder carType(final String carType) {
        this.carType = carType;
        return this;
    }

    public Car$CarBuilder engine(final Engine engine) {
        this.engine = engine;
        return this;
    }

    public Car$CarBuilder number(final Integer number) {
        this.number = number;
        return this;
    }

    public Car$CarBuilder place(final String place) {
        this.place = place;
        return this;
    }

    public Car build() {
        return new Car(this.carType, this.engine, this.number, this.place);
    }

    public String toString() {
        return "Car.CarBuilder(carType=" + this.carType + ", engine=" + this.engine + ", number=" + this.number + ", place=" + this.place + ")";
    }
}

它的每一个属性设置方法都会返回该构造器本身,因此我们可以有选择性地传入我们所需的参数,同时进行链式的对象构造。
值得一提的是,构造对象虽然可以通过链式build,但是完成构造的对象却无法再进行链式设值。这一点来看,还是@Accessors更适合我_(:з」∠)_。

Q.E.D.