设计模式之建造者模式


前言

最近接手的项目遇到了比较大的坑,在实现流量套餐业务时(有贵宾、高级贵宾、WiFi、导航等等套餐),在其实现的code中光if…else…就超过了100行以上,此时心里一万只羊驼跑过…,最近刚好在学习设计模式,这类的业务可以用建造者模式设计,便先写下这篇作者对建造者模式理解,希望能帮到你。

为什么学习设计模式?

  1. 领域建模能力 将复杂业务合理拆分;
  2. 提升自身功能设计和code能力;
  3. code可读性强,扩展性强;
  4. 提升阅读框架源码能力;
  5. 面试加分项。

设计模式分类

  1. 创建型模式:提供创建对象的机制,提升已有代码的灵活性和可复用性;
  2. 结构型模式:介绍如何将对象和类组装成较大的结构,并同时保持结构的灵活和高效;
  3. 行为型模式:负载对象间的高效沟通和职责传递委派。

本篇介绍的建造者模式就是属于 创建型模式。

需求模拟

为了让建造者模式有温度,这里模拟一个业务需求,通过2种方式(if…else方式和设计模式)来实现业务需求让大家更好的理解建造者模式。

业务需求如下:
装修公司装修3种套餐:豪华欧式、轻奢田园、现代简约套餐。而这些套餐的背后是不同装修材料和设计风格的组合,如 一级顶、二级顶、多乐士涂料、立邦涂料、圣象地板、德尔地板、马可波罗地砖、东鹏地砖等。按照不同的套餐价格,选取不同的品牌进行组合,最终再结合装修面积给出整体报价。

可以思考一下,如果让你来实现,你会怎么设计?

场景模拟工程

1.装修材料接口

装修材料接口提供了基本方法获取信息,以保证所有不同规格和种类的装修材料都可以按照统一标准获取。

代码如下(示例):

/**
 * 装修材料接口
 * @author winter
 */
public interface Matter {
    /**
     * 场景:地板、地砖、涂料、吊顶
     * @return
     */
    public String scene();

    /**
     * 品牌
     * @return
     */
    public String brand();

    /**
     * 型号
     * @return
     */
    public String model();

    /**
     * 价格
     * @return
     */
    public BigDecimal price();

    /**
     * 描述
     * @return
     */
    public String desc();

}

2.吊顶材料

代码如下(示例):

/**
 * 一级顶的吊顶材料
 * @author winter
 */
public class LevelOneCelling implements Matter {
    @Override
    public String scene() {
        return "吊顶";
    }

    @Override
    public String brand() {
        return "装修公司";
    }

    @Override
    public String model() {
        return "一级顶";
    }

    @Override
    public BigDecimal price() {
        return new BigDecimal(260);
    }

    @Override
    public String desc() {
        return "一级顶...";
    }
}
/**
 * 二级顶的吊顶材料
 * @author winter
 */
public class LevelTwoCelling implements Matter {
    @Override
    public String scene() {
        return "吊顶";
    }

    @Override
    public String brand() {
        return "装修公司";
    }

    @Override
    public String model() {
        return "二级顶";
    }

    @Override
    public BigDecimal price() {
        return new BigDecimal(850);
    }

    @Override
    public String desc() {
        return "二级吊顶...";
    }
}

3.涂料材料

代码如下(示例):

/**
 * 多乐士 涂料
 * @author winter
 */
public class DuluxCoat implements Matter {
    @Override
    public String scene() {
        return "涂料";
    }

    @Override
    public String brand() {
        return "多乐士";
    }

    @Override
    public String model() {
        return "第二代";
    }

    @Override
    public BigDecimal price() {
        return new BigDecimal(719);
    }

    @Override
    public String desc() {
        return "多乐士涂料......";
    }
}
/**
 * 立邦 涂料
 * @author winter
 */
public class LiBangCoat implements Matter {
    @Override
    public String scene() {
        return "涂料";
    }

    @Override
    public String brand() {
        return "立邦";
    }

    @Override
    public String model() {
        return "最新代";
    }

    @Override
    public BigDecimal price() {
        return new BigDecimal(650);
    }

    @Override
    public String desc() {
        return "立邦涂料......";
    }
}

4.地板材料

代码如下(示例):

/**
 * 德尔 地板材料
 * @author winter
 */
public class DerFloor implements Matter {

    @Override
    public String scene() {
        return "地板";
    }

    @Override
    public String brand() {
        return "德尔";
    }

    @Override
    public String model() {
        return "A+";
    }

    @Override
    public BigDecimal price() {
        return new BigDecimal(119);
    }

    @Override
    public String desc() {
        return "德尔地板...";
    }
}
/**
 * 圣象 地板材料
 * @author winter
 */
public class ShengXiangFloor implements Matter {

    @Override
    public String scene() {
        return "地板";
    }

    @Override
    public String brand() {
        return "圣象";
    }

    @Override
    public String model() {
        return "一级";
    }

    @Override
    public BigDecimal price() {
        return new BigDecimal(318);
    }

    @Override
    public String desc() {
        return "圣象地板...";
    }
}

5.地砖材料

代码如下(示例):

/**
 * 东鹏 地砖材料
 * @author winter
 */
public class DongPengTile implements Matter {
    @Override
    public String scene() {
        return "地砖";
    }

    @Override
    public String brand() {
        return "东鹏瓷砖";
    }

    @Override
    public String model() {
        return "1001";
    }

    @Override
    public BigDecimal price() {
        return new BigDecimal(102);
    }

    @Override
    public String desc() {
        return "东鹏瓷砖...";
    }
}
/**
 * 马可波罗 地砖材料
 * @author winter
 */
public class MarcoPoloTile implements Matter {
    @Override
    public String scene() {
        return "地砖";
    }

    @Override
    public String brand() {
        return "马可波罗";
    }

    @Override
    public String model() {
        return "默认";
    }

    @Override
    public BigDecimal price() {
        return new BigDecimal(140);
    }

    @Override
    public String desc() {
        return "马可波罗地砖...";
    }
}

违背设计模式实现

1.if…else实现需求

代码如下(示例):

/**
 * 装修套餐实现类  违背设计模式实现
 * @author winter
 */
public class DecorationPackageController {

    /**
     * 装修套餐方法
     * @param area 面积
     * @param level 装修类型
     * @return
     */
    public String getMatterList(BigDecimal area, Integer level) {
        // 装修清单
        List<Matter> list = new ArrayList<Matter>();
        // 装修价格
        BigDecimal price = BigDecimal.ZERO;

        // 豪华欧式
        if (1 == level) {
            // 吊顶,二级顶
            LevelTwoCelling levelTwoCeiling = new LevelTwoCelling();
            // 涂料,多乐士
            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) {
            // 吊顶,二级顶
            LevelTwoCelling levelTwoCeiling = new LevelTwoCelling();
            // 涂料,立邦
            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) {
            // 吊顶,二级顶
            LevelOneCelling levelOneCeiling = new LevelOneCelling();
            // 涂料,立邦
            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();

    }
}

2.单元测试

代码如下(示例):

@SpringBootTest
class BuilderApplicationTests {
    @Test
    void decorationPackageControllerTest() {
        DecorationPackageController decoration = new DecorationPackageController();
        // 豪华欧式
        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));
    }
}

3.单元测试结果

装修清单
套餐等级:1
套餐价格:198064.39 元
房屋面积:132.52 平米
材料清单:
吊顶:装修公司、二级顶、平米价格:850 元。
涂料:多乐士、第二代、平米价格:719 元。
地板:圣象、一级、平米价格:318 元。


装修清单
套餐等级:2
套餐价格:119865.00 元
房屋面积:98.25 平米
材料清单:
吊顶:装修公司、二级顶、平米价格:850 元。
涂料:立邦、最新代、平米价格:650 元。
地砖:马可波罗、默认、平米价格:140 元。


装修清单
套餐等级:3
套餐价格:90897.52 元
房屋面积:85.43 平米
材料清单:
吊顶:装修公司、一级顶、平米价格:260 元。
涂料:立邦、最新代、平米价格:650 元。
地砖:东鹏瓷砖、1001、平米价格:102 元。

建造者模式重构

1.模式的结构

建造者(Builder)模式的主要角色如下:

  1. 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
  2. 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
  3. 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
  4. 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。

其结构如下:
在这里插入图片描述

2.产品角色

产品角色: 包含多个组成部件的复杂对象。
代码如下(示例):
接口定义了填充吊顶、涂料、地板、地砖等各种方法

/**
 * 产品菜单
 * @author winter
 */
public interface ProductMenu {
    /**
     * 吊顶
     * @param matter 物料接口
     * @return
     */
    ProductMenu appendCelling(Matter matter);
    /**
     * 涂料
     * @param matter 物料接口
     * @return
     */
    ProductMenu appendCoat(Matter matter);
    /**
     * 地板
     * @param matter 物料接口
     * @return
     */
    ProductMenu appendFloor(Matter matter);
    /**
     * 地砖
     * @param matter 物料接口
     * @return
     */
    ProductMenu appendTile(Matter matter);

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

产品角色类实现类

/**
 * 装修产品
 * @author winter
 */
public class Product implements ProductMenu {
    /**
     * 装修清单
     */
    private List<Matter> list =new ArrayList<>();
    /**
     * 装修价格
     */
    private BigDecimal price = BigDecimal.ZERO;
    /**
     * 装修面积
     */
    private BigDecimal area;
    /**
     * 装修等级 豪华欧式、轻奢田园、现代简约
     */
    private String grade;

    public Product() {
    }

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

    @Override
    public ProductMenu appendCelling(Matter matter) {
        list.add(matter);
        price = price.add(area.multiply(new BigDecimal("0.2")).multiply(matter.price()));
        return this;
    }

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

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

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

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

3.抽象建造者

包含创建产品各个子部件的抽象方法

代码如下(示例)

/**
 * 抽象建造者
 * @author winter
 */
abstract class Builder {
    /**
     * 创建产品对象
     */
    protected Product product = new Product();

    /**
     * 豪华装修
     * @return
     */
    public abstract ProductMenu levelOne(Double area);

    /**
     * 轻奢田园
     * @return
     */
    public abstract ProductMenu levelTwo(Double area);

    /**
     * 现代简约
     * @return
     */
    public abstract ProductMenu levelThree(Double area);

    /**
     * 返回产品结果
     * @return
     */
    public Product getResult() {
        return product;
    }
}

4.具体建造者

代码如下(示例)

/**
 * 具体建造者
 * @author winter
 */
public class ConcreteBuilder extends Builder{

    @Override
    public ProductMenu levelOne(Double area) {
        product =new Product(area, "豪华欧式");
        // 吊顶,二级顶
        return product.appendCelling(new LevelTwoCelling())
                // 涂料,多乐士
                .appendCoat(new DuluxCoat())
                // 地板,圣象
                .appendFloor(new ShengXiangFloor());
    }

    @Override
    public ProductMenu levelTwo(Double area) {
        product =new Product(area, "轻奢田园");
        // 吊顶,二级顶
        return product.appendCelling(new LevelTwoCelling())
                // 涂料,立邦
                .appendCoat(new LiBangCoat())
                // 地砖,马可波罗
                .appendTile(new MarcoPoloTile());
    }

    @Override
    public ProductMenu levelThree(Double area) {
        product =new Product(area, "现代简约");
        // 吊顶,二级顶
        return product.appendCelling(new LevelOneCelling())
                // 涂料,立邦
                .appendCoat(new LiBangCoat())
                // 地砖,东鹏;
                .appendTile(new DongPengTile());
    }
}

5.指挥者

调用建造者中的方法完成复杂对象的创建。

/**
 * 指挥者
 * @author winter
 */
public class Director {
    private Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }

    /**
     * 产品构建与组装方法
     * @return
     */
    public String constructOne(Double area) {
        builder.levelOne(area);
        return builder.getResult().getDetail();
    }
    /**
     * 产品构建与组装方法
     * @return
     */
    public String constructTwo(Double area) {
        builder.levelTwo(area);
        return builder.getResult().getDetail();
    }
    /**
     * 产品构建与组装方法
     * @return
     */
    public String constructThree(Double area) {
        builder.levelThree(area);
        return builder.getResult().getDetail();
    }
}

6.客户类(测试)

代码如下(示例)

public class Client {
    public static void main(String[] args) {
        Builder builder = new ConcreteBuilder();
        Director director = new Director(builder);
        //豪华装修
        String result1 = director.constructOne(132.52);
        System.out.println(result1);
        //轻奢田园
        String result2 = director.constructTwo(98.25);
        System.out.println(result2);
        //现代简约
        String result3 = director.constructThree(85.43);
        System.out.println(result3);
    }
}

7.测试结果


装修清单
套餐等级:豪华欧式
套餐价格:198064.39 元
房屋面积:132.52 平米
材料清单:
吊顶:装修公司、二级顶、平米价格:850 元。
涂料:多乐士、第二代、平米价格:719 元。
地板:圣象、一级、平米价格:318 元。


装修清单
套餐等级:轻奢田园
套餐价格:119865.00 元
Disconnected from the target VM, address: ‘127.0.0.1:49890’, transport: ‘socket’
房屋面积:98.25 平米
材料清单:
吊顶:装修公司、二级顶、平米价格:850 元。
涂料:立邦、最新代、平米价格:650 元。
地砖:马可波罗、默认、平米价格:140 元。


装修清单
套餐等级:现代简约
套餐价格:90897.52 元
房屋面积:85.43 平米
材料清单:
吊顶:装修公司、一级顶、平米价格:260 元。
涂料:立邦、最新代、平米价格:650 元。
地砖:东鹏瓷砖、1001、平米价格:102 元。

执行结果和之前用if…else实现的结果一致。

总结

需求和案例是参考的《重学java设计模式》这本书,在使用建造者模式重构该业务时并没有按照书上方式实现(书上实现方式有简化),而是通过自己的理解严格按照建造者模式的结构实现。
通过上面对建造者模式的使用可以总结出:

  1. 建造者模式选择条件:当一些基本材料不变,而组合经常变化(如套餐);
  2. 建造者模式满足了单一职责原则及可复用技术,建造者独立,易扩展、便于扩展细节风险。

都看到最后了,请帮忙一键三连哦!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值