likes
comments
collection
share

3.生成器(建造者)

作者站长头像
站长
· 阅读数 32

生成器(建造者)

3.生成器(建造者)

建造者模式; 高端说法是,使你能够分布创建复杂对象。该模式允许你使用相同的创建代码生成不同类型和形式的对象。 通俗的讲,就是多个简单对象通过一步步组装称为一个复杂对象的过程。 低俗的讲,就相当于虽然都是房子,但有的是别墅精装,有的是穷装。家具不同,电器牌子也不同,但他们最后都生成了一个复杂对象(装修好的房子)。人类的悲欢离合并不相通。

结构实现

3.生成器(建造者)

  1. 生成器(Bulid):定义构建产品各个部分的抽象接口的方法
  2. 具体生成器(Concrete Builder):实现生成器的接口,构建产品的各个组成部分,并提供一个方法返回最终的产品。
  3. 产品(Product):被构建的复杂对象,包含多个组成部分。
  4. 主管(Director):调用构造者的具体方法,按照一定顺序或者逻辑来生成产品
  5. 客户端(Client):告诉主管获取产品,该结构可有可无。

按图索骥

老规矩,我们拿车车来举例子,车型又可以分为小车,suv等,座位也可以分为五座,六座等,配饰也细分不同,而我们也要根据不同配置生产不同Manual(说明书) 产品类如下:

import lombok.Data;

/**
 * @title: Car
 * @date: 2024/4/24 下午2:12
 * @description: 
 */

@Data
public class Car {

    private int seats;

    private String engine;

    private String tripComputer;

    private String gps;
}
import lombok.Data;

/**
 * @title: Manual
 * @date: 2024/4/24 下午2:20
 * @description: 
 */

@Data
public class Manual {

    private int seats;

    private String engine;

    private String tripComputer;

    private String gps;

    // 私有属性xxx

}

抽象生成器

/**
 * @title: Builder
 * @date: 2024/4/24 下午2:22
 * @description:
 */
public interface Builder {
    void reset();
    void setSeats(int num);
    void setEngine(String engine);
    void setTripComputer(String tripComputer);
    void setGPS(String gps);
}

具体生产器

/**
 * @title: CarBuilder
 * @date: 2024/4/24 下午2:26
 * @description:
 */
public class CarBuilder implements Builder {

    private Car car;

    public CarBuilder() {
        this.reset();
    }

    @Override
    public void reset() {
        this.car = new Car();
    }

    @Override
    public void setSeats(int num) {
        this.car.setSeats(num);
    }

    @Override
    public void setEngine(String engine) {
        this.car.setEngine(engine);
    }

    @Override
    public void setTripComputer(String tripComputer) {
        this.car.setTripComputer(tripComputer);
    }

    @Override
    public void setGPS(String gps) {
        this.car.setGps(gps);
    }

    public Car getCar() {
        Car car = this.car;
        this.reset();
        return car;
    }

}

/**
 * @title: CarManualBuilder
 * @date: 2024/4/24 下午2:54
 * @description:
 */
public class CarManualBuilder implements Builder{

    private Manual manual;


    @Override
    public void reset() {
        this.manual = new Manual();
    }

    @Override
    public void setSeats(int num) {
        this.manual.setSeats(num);
    }

    @Override
    public void setEngine(String engine) {
        this.manual.setEngine(engine);
    }

    @Override
    public void setTripComputer(String tripComputer) {
        this.manual.setTripComputer(tripComputer);
    }

    @Override
    public void setGPS(String gps) {
        this.manual.setGps(gps);
    }


}

总监

/**
 * @title: Direct
 * @date: 2024/4/24 下午5:49
 * @description:
 */


public class Direct {

    // 以下可要可不要
//    private Builder builder;
//
//    void setBuilder(Builder builder) {
//        this.builder = builder;
//    }

    void constructSportsCar(Builder builder) {
        builder.reset();
        builder.setSeats(2);
        builder.setEngine("马自达");
        builder.setGPS("true");
    }

}

测试

public class Test {

    @org.junit.Test
    public void test() {
        Direct direct = new Direct();
        CarBuilder builder = new CarBuilder();
        direct.constructSportsCar(builder);
        Car car = builder.getCar();
        System.out.println(car.toString());
    }
}

好了,按图索骥环节到此结束,当然以上代码会给人一种疑问,那就是生成器不是要返回一个复杂对象吗? 而以上代码我只看到了两个一模一样的对象,你这个demo也没看出来有什么用呀。 莫急莫急,听小羊缓慢到来。 通过以上demo,我们可以观察出生成器可以创建不遵循相同接口的产品,即可以避免“重叠构函数”的出现。也就是使用代码创建不同形式的产品(也就是车辆和车辆说明书)。

说这么多,可能也有点懵逼,没事,下面请看实战环节。

实例说明

很多产品都会推出自家的(或者包含合作公司的产品)套餐服务,让我们拿装修公司来举例,一般有装修风格包含欧式豪华,轻奢,现代简约等,而这些套餐又根据价格不同推出不同的商品组合。 那我们简单以吊顶,地板,地砖,涂料举例说明

问题说明

目前有物料接口

public interface Matter {

    /**
     * 场景;地板、地砖、涂料、吊顶
     */
    String scene();

    /**
     * 品牌
     */
    String brand();

    /**
     * 型号
     */
    String model();

    /**
     * 平米报价
     */
    BigDecimal price();

    /**
     * 描述
     */
    String desc();
}

目前产品说明:

产品名类名以及说明
吊顶LevelOneCeiling(一级顶)、LevelTwoCeiling(二级顶)
涂料DuluxCoat(多乐士)、LiBangCoat(立邦)
地板DerFloor(德尔)、ShengXiangFloor (圣像)
地砖DongPengTile(东鹏)、MarcoPoloTile(马可波罗)

以上每个产品的类只是继承了 Matter 接口,在对应的重写方法中输出不同数据。

if - else 实现

请记住一点,没有什么是 if-else实现不了的,如果有,那就是你if的不够。 请看实现代码:

public class DecorationPackageController {

    public String getMatterList(BigDecimal area, Integer level) {

        List<Matter> list = new ArrayList<Matter>(); // 装修清单
        BigDecimal price = BigDecimal.ZERO;          // 装修价格

        // 豪华欧式
        if (1 == level) {

            LevelTwoCeiling levelTwoCeiling = new LevelTwoCeiling(); // 吊顶,二级顶
            DuluxCoat duluxCoat = new DuluxCoat();                   // 涂料,多乐士
            ShengXiangFloor shengXiangFloor = new ShengXiangFloor(); // 地板,圣象

            list.add(levelTwoCeiling);
            list.add(duluxCoat);
            list.add(shengXiangFloor);

            price = price.add(area.multiply(new BigDecimal("0.2")).multiply(levelTwoCeiling.price()));
            price = price.add(area.multiply(new BigDecimal("1.4")).multiply(duluxCoat.price()));
            price = price.add(area.multiply(shengXiangFloor.price()));

        }

        // 轻奢田园
        if (2 == level) {

            LevelTwoCeiling levelTwoCeiling = new LevelTwoCeiling(); // 吊顶,二级顶
            LiBangCoat liBangCoat = new LiBangCoat();                // 涂料,立邦
            MarcoPoloTile marcoPoloTile = new MarcoPoloTile();       // 地砖,马可波罗

            list.add(levelTwoCeiling);
            list.add(liBangCoat);
            list.add(marcoPoloTile);

            price = price.add(area.multiply(new BigDecimal("0.2")).multiply(levelTwoCeiling.price()));
            price = price.add(area.multiply(new BigDecimal("1.4")).multiply(liBangCoat.price()));
            price = price.add(area.multiply(marcoPoloTile.price()));

        }

        // 现代简约
        if (3 == level) {

            LevelOneCeiling levelOneCeiling = new LevelOneCeiling();  // 吊顶,二级顶
            LiBangCoat liBangCoat = new LiBangCoat();                 // 涂料,立邦
            DongPengTile dongPengTile = new DongPengTile();           // 地砖,东鹏

            list.add(levelOneCeiling);
            list.add(liBangCoat);
            list.add(dongPengTile);

            price = price.add(area.multiply(new BigDecimal("0.2")).multiply(levelOneCeiling.price()));
            price = price.add(area.multiply(new BigDecimal("1.4")).multiply(liBangCoat.price()));
            price = price.add(area.multiply(dongPengTile.price()));
        }

        StringBuilder detail = new StringBuilder("\r\n-------------------------------------------------------\r\n" +
                "装修清单" + "\r\n" +
                "套餐等级:" + level + "\r\n" +
                "套餐价格:" + price.setScale(2, BigDecimal.ROUND_HALF_UP) + " 元\r\n" +
                "房屋面积:" + area.doubleValue() + " 平米\r\n" +
                "材料清单:\r\n");

        for (Matter matter: list) {
            detail.append(matter.scene()).append(":").append(matter.brand()).append("、").append(matter.model()).append("、平米价格:").append(matter.price()).append(" 元。\n");
        }
        return detail.toString();
    }
}

测试

 System.out.println(decoration.getMatterList(new BigDecimal("132.52"),1));
 
 System.out.println(decoration.getMatterList(new BigDecimal("98.25"),2));
 
 System.out.println(decoration.getMatterList(new BigDecimal("85.43"),3));

相比通过以上demo,我们需要根据空间和装修风格,生成不同材料,不同面积的装修套餐,而三种就如此复杂,要是公司扩宽业务可能就需要成千行代码来实现了,也不利于以后维护。

建造者模式

老规矩,我们得先抽象出来生成器,很明显,这个生成器就是我们最后的报价单啦。

public interface IMenu {

    /**
     * 吊顶
     */
    IMenu appendCeiling(Matter matter);

    /**
     * 涂料
     */
    IMenu appendCoat(Matter matter);

    /**
     * 地板
     */
    IMenu appendFloor(Matter matter);

    /**
     * 地砖
     */
    IMenu appendTile(Matter matter);

    /**
     * 明细
     */
    String getDetail();
}

在分析出来生成器后,我们就可以参考结构图一一实现具体生成器来返回产品。(忽略重置方法)

/**
 * 装修包
 */
public class DecorationPackageMenu implements IMenu {

    private List<Matter> list = new ArrayList<Matter>();  // 装修清单
    private BigDecimal price = BigDecimal.ZERO;      // 装修价格

    private BigDecimal area;  // 面积
    private String grade;     // 装修等级;豪华欧式、轻奢田园、现代简约

    private DecorationPackageMenu() {
    }

    public DecorationPackageMenu(Double area, String grade) {
        this.area = new BigDecimal(area);
        this.grade = grade;
    }

    public IMenu appendCeiling(Matter matter) {
        list.add(matter);
        price = price.add(area.multiply(new BigDecimal("0.2")).multiply(matter.price()));
        return this;
    }

    public IMenu appendCoat(Matter matter) {
        list.add(matter);
        price = price.add(area.multiply(new BigDecimal("1.4")).multiply(matter.price()));
        return this;
    }

    public IMenu appendFloor(Matter matter) {
        list.add(matter);
        price = price.add(area.multiply(matter.price()));
        return this;
    }

    public IMenu appendTile(Matter matter) {
        list.add(matter);
        price = price.add(area.multiply(matter.price()));
        return this;
    }

    /**
     * 返回产品
     * @return
     */
    public String getDetail() {
        StringBuilder detail = new StringBuilder("\r\n-------------------------------------------------------\r\n" +
                "装修清单" + "\r\n" +
                "套餐等级:" + grade + "\r\n" +
                "套餐价格:" + price.setScale(2, BigDecimal.ROUND_HALF_UP) + " 元\r\n" +
                "房屋面积:" + area.doubleValue() + " 平米\r\n" +
                "材料清单:\r\n");
        for (Matter matter: list) {
            detail.append(matter.scene()).append(":").append(matter.brand()).append("、").append(matter.model()).append("、平米价格:").append(matter.price()).append(" 元。\n");
        }
        return detail.toString();
    }

}

当我们拿到具体生产者后,就可以去找主管生成具体的产品了。

public class Builder {

    public IMenu levelOne(Double area) {
        return new DecorationPackageMenu(area, "豪华欧式")
                .appendCeiling(new LevelTwoCeiling())    // 吊顶,二级顶
                .appendCoat(new DuluxCoat())             // 涂料,多乐士
                .appendFloor(new ShengXiangFloor());     // 地板,圣象
    }

    public IMenu levelTwo(Double area){
        return new DecorationPackageMenu(area, "轻奢田园")
                .appendCeiling(new LevelTwoCeiling())   // 吊顶,二级顶
                .appendCoat(new LiBangCoat())           // 涂料,立邦
                .appendTile(new MarcoPoloTile());       // 地砖,马可波罗
    }

    public IMenu levelThree(Double area){
        return new DecorationPackageMenu(area, "现代简约")
                .appendCeiling(new LevelOneCeiling())   // 吊顶,二级顶
                .appendCoat(new LiBangCoat())           // 涂料,立邦
                .appendTile(new DongPengTile());        // 地砖,东鹏

}

测试方法

public void test_Builder(){
        Builder builder = new Builder();

        // 豪华欧式
        System.out.println(builder.levelOne(132.52D).getDetail());

        // 轻奢田园
        System.out.println(builder.levelTwo(98.25D).getDetail());

        // 现代简约
        System.out.println(builder.levelThree(85.43D).getDetail());
    }

以上,我们就利用了生成器模式小小的重构了一下代码。虽然增加了类,但是在结构上更加清晰明了,以后优化起来也很方便,只需要在 BUilder上新增新的风格就可以啦。

总结

更具按图索骥和实战分析环节,我们大概能明白生成器模式使用的场景,那就是当一些基本变量不变,而且组合经常变化的时候,就可以选择使用生成器模式了。 但是请记住,设计模式虽好,但不要贪杯。正如总结篇所说:“当你手上有一把锤子,那你会觉得整个房间都是钉子”所言,滥用生成器模式可能会是你的结构复杂化,就好比该业务本来就只需要一个if-else判断,但是你偏偏用设计模式去实现,这就有点本末倒置了,违背了我们想让代码更加规范简洁的初心啦。

参考资料

  1. 设计模式 | 菜鸟教程 (runoob.com)
  2. 《深入设计模式》 PDF版
  3. 付政委. 重学Java设计模式(全彩). 电子工业出版社, 2021.
转载自:https://juejin.cn/post/7362739494495010850
评论
请登录