侧边栏壁纸
博主头像
qingtian博主等级

喜欢是一件细水流长的事,是永不疲惫的双向奔赴~!

  • 累计撰写 104 篇文章
  • 累计创建 48 个标签
  • 累计收到 1 条评论

设计模式之建造者模式

qingtian
2020-11-04 / 0 评论 / 1 点赞 / 1,042 阅读 / 14,619 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2020-12-31,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

设计模式之建造者模式

来源自《重学Java设计模式》链接提取码:ytc3

建造者模式

建造者模式所完成的内容就是通过将多个简单对象通过⼀一步步的组装构建出⼀一个复杂对象的过程。

⽽而这样的根据相同的 物料料,不不同的组装所产⽣生出的具体的内容,就是建造者模式的最终意图,也就是; 将⼀一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不不同的表示。

案例场景模拟

这里我们模拟装修公司对于设计出⼀一些套餐装修服务的场景。

很多装修公司都会给出⾃自家的套餐服务,⼀一般有;欧式豪华、轻奢⽥田园、现代简约等等,⽽而这些套餐的 后⾯面是不不同的商品的组合。例例如;⼀一级&⼆二级吊顶、多乐⼠士涂料料、圣象地板、⻢马可波罗地砖等等,按照 不不同的套餐的价格选取不不同的品牌组合,最终再按照装修⾯面积给出⼀一个整体的报价。
这⾥里里我们就模拟装修公司想推出⼀一些套餐装修服务,按照不不同的价格设定品牌选择组合,以达到使⽤用建 造者模式的过程。

场景简介

物料接口

/**
 * 装修物料
 */
public interface Matter {

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

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

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

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

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

}
  • 物料料接⼝口提供了了基本的信息,以保证所有的装修材料料都可以按照统⼀一标准进⾏行行获取。

吊顶(ceiling)

一级顶

/**
 * 吊顶
 * 品牌;装修公司自带
 * 型号:一级顶
 */
public class LevelOneCeiling implements Matter {

    public String scene() {
        return "吊顶";
    }

    public String brand() {
        return "装修公司自带";
    }

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

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

    public String desc() {
        return "造型只做低一级,只有一个层次的吊顶,一般离顶120-150mm";
    }

}

二级顶

/**
 * 吊顶
 * 品牌;装修公司自带
 * 型号:二级顶
 */
public class LevelTwoCeiling  implements Matter {

    public String scene() {
        return "吊顶";
    }

    public String brand() {
        return "装修公司自带";
    }

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

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

    public String desc() {
        return "两个层次的吊顶,二级吊顶高度一般就往下吊20cm,要是层高很高,也可增加每级的厚度";
    }
    
}

涂料

多乐⼠

/**
 * 涂料
 * 品牌;多乐士(Dulux)
 */
public class DuluxCoat  implements Matter {

    public String scene() {
        return "涂料";
    }

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

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

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

    public String desc() {
        return "多乐士是阿克苏诺贝尔旗下的著名建筑装饰油漆品牌,产品畅销于全球100个国家,每年全球有5000万户家庭使用多乐士油漆。";
    }
    
}

立邦

/**
 * 涂料
 * 品牌;立邦
 */
public class LiBangCoat implements Matter {

    public String scene() {
        return "涂料";
    }

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

    public String model() {
        return "默认级别";
    }

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

    public String desc() {
        return "立邦始终以开发绿色产品、注重高科技、高品质为目标,以技术力量不断推进科研和开发,满足消费者需求。";
    }

}

地板

/**
 * 地板
 * 品牌;德尔(Der)
 */
public class DerFloor implements Matter {

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

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

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

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

    public String desc() {
        return "DER德尔集团是全球领先的专业木地板制造商,北京2008年奥运会家装和公装地板供应商";
    }
    
}

圣象

/**
 * 地板
 * 品牌:圣象
 */
public class ShengXiangFloor implements Matter {

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

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

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

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

    public String desc() {
        return "圣象地板是中国地板行业著名品牌。圣象地板拥有中国驰名商标、中国名牌、国家免检、中国环境标志认证等多项荣誉。";
    }

}

地砖

/**
 * 地砖
 * 品牌:东鹏瓷砖
 */
public class DongPengTile implements Matter {

    public String scene() {
        return "地砖";
    }

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

    public String model() {
        return "10001";
    }

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

    public String desc() {
        return "东鹏瓷砖以品质铸就品牌,科技推动品牌,口碑传播品牌为宗旨,2014年品牌价值132.35亿元,位列建陶行业榜首。";
    }

}
/**
 * 地砖
 * 品牌;马可波罗(MARCO POLO)
 */
public class MarcoPoloTile implements Matter {

    public String scene() {
        return "地砖";
    }

    public String brand() {
        return "马可波罗(MARCO POLO)";
    }

    public String model() {
        return "缺省";
    }

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

    public String desc() {
        return "“马可波罗”品牌诞生于1996年,作为国内最早品牌化的建陶品牌,以“文化陶瓷”占领市场,享有“仿古砖至尊”的美誉。";
    }

}
  • 以上就是本次装修公司所提供的装修配置单,接下我们会通过案例例去使⽤用不不同的物料料组合出不不同的 套餐服务。

ifelse实现需求

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

    }

}
  • 首先这段代码所要解决的问题就是接收入参;装修⾯面积(area)、装修等级(level),根据不不同类型的 装修等级选择不不同的材料料。
  • 其次在实现过程中可以看到每一段 if 块里,都包含着不同的材料(吊顶,二级顶、涂料,立邦、地 砖,马可波罗),最终生成装修清单和装修成本。
    最后提供获取装修详细信息的⽅方法,返回给调⽤方,用于知道装修清单。

测试验证

public class ApiTest {

    @Test
    public void test_DecorationPackageController(){
        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));
    }

}

result

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


-------------------------------------------------------
装修清单
套餐等级:2
套餐价格:119865.00 元
房屋面积:98.25 平米
材料清单:
吊顶:装修公司自带、二级顶、平米价格:850 元。
涂料:立邦、默认级别、平米价格:650 元。
地砖:马可波罗(MARCO POLO)、缺省、平米价格:140 元。


-------------------------------------------------------
装修清单
套餐等级:3
套餐价格:90897.52 元
房屋面积:85.43 平米
材料清单:
吊顶:装修公司自带、一级顶、平米价格:260 元。
涂料:立邦、默认级别、平米价格:650 元。
地砖:东鹏瓷砖、10001、平米价格:102 元。
  • 看到输出的这个结果,已经很有装修公司提供报价单的感觉了了。以上这段使用 ifelse ⽅方式实现的 代码,⽬目前已经满⾜足的我们的也许功能。但随着老板对业务的快速发展要求,会提供很多的套餐针 对不不同的户型。那么这段实现代码将迅速扩增到几千行,甚⾄至在修修改改中,已经像膏药⼀一样难以 维护。

利用建造者模式重构代码

建造者模式主要解决的问题是在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的过程构成;由于需求的变化,这个复杂对象的各个部分经常面临着重大的变化,但是将它们组合在一起的过程去相对稳定。

这里我们会把构建的过程交给创建者类,而创建者用过使用我们的构建工具包,去构建出不同的装修套餐

建造者模型结构

sda

工程中三个核心类和一个测试类,核心类是建造者模式的具体实现。与ifelse实现方式相比,多出来了两个二外的类。具体功能如下;

  • Builder,建造者类具体的各种组装有此类实现。
  • DecorationPackageMenu,是IMenu接口的实现类,主要是承载建造过程中的填充器。相当于这是一套承载物料和创建者中间衔接的内容。

代码实现

  1. 定义装修包接口

    public interface IMenu {
    
        /**
         * 吊顶
         */
        IMenu appendCeiling(Matter matter);
    
        /**
         * 涂料
         */
        IMenu appendCoat(Matter matter);
    
        /**
         * 地板
         */
        IMenu appendFloor(Matter matter);
    
        /**
         * 地砖
         */
        IMenu appendTile(Matter matter);
    
        /**
         * 明细
         */
        String getDetail();
    
    }
    
    • 接口类中定义了了填充各项物料的方法;吊顶、涂料料、地板、地砖,以及最终提供获取全部明细 的方法。
  2. 装修包实现

    /**
     * 装修包
     */
    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;
        }
    
        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();
        }
    
    }
    
    • 装修包的实现中每一个方法都返回了this,也就可以非常方便的用于连续填充各项物料。(链式连续编程!)
    • 同时在填充时也会根据物料计算平米数下的报价,吊顶和涂料按照平米数适量乘以常数来计算。
    • 最后同样提供统一的获得装修清单的明细方法。
  3. 建造者方法

    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());        // 地砖,东鹏
        }
    
    }
    
    • 建造者的使用中就已经非常容易了,统一的建造方式,通过不同物料填充出不同的装修风格;豪华 欧式轻奢田园现代简约,如果将来业务扩展也可以将这部分内容配置到数据库自动生成。但整体的思想还可以使用创建这模式进行搭建。
  4. 测试

    测试类

    public class ApiTest {
    
        @Test
        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());
        }
    
    }
    

    result

    -------------------------------------------------------
    装修清单
    套餐等级:豪华欧式
    套餐价格:198064.39 元
    房屋面积:132.52 平米
    材料清单:
    吊顶:装修公司自带、二级顶、平米价格:850 元。
    涂料:多乐士(Dulux)、第二代、平米价格:719 元。
    地板:圣象、一级、平米价格:318 元。
    
    
    -------------------------------------------------------
    装修清单
    套餐等级:轻奢田园
    套餐价格:119865.00 元
    房屋面积:98.25 平米
    材料清单:
    吊顶:装修公司自带、二级顶、平米价格:850 元。
    涂料:立邦、默认级别、平米价格:650 元。
    地砖:马可波罗(MARCO POLO)、缺省、平米价格:140 元。
    
    
    -------------------------------------------------------
    装修清单
    套餐等级:现代简约
    套餐价格:90897.52 元
    房屋面积:85.43 平米
    材料清单:
    吊顶:装修公司自带、一级顶、平米价格:260 元。
    涂料:立邦、默认级别、平米价格:650 元。
    地砖:东鹏瓷砖、10001、平米价格:102 元。
    
    
    • 测试结果是一样的,调用的方法也基本类似。但是目前的代码结构却可以让你很方便的很有条理的进行扩展业务开发,而不是以往一样把所有代码都写到ifelse里面。

总结

  • 通过上面对建造者模式的使用,已经可以摸索出一点心得。那就是什么时候会选择使用这样的设计模式,当:一些基本物料不变时,而其通过物料间的不同组合来实现不同需求时,就可以选择建造者模式来构建代码。
  • 此设计模式满足了单一职责原则以及可复用的技术,类的不断扩展也会造成难以维护的问题。但这种设计结构模型可以把重复的内容抽象到数据库中,按照需要配置。这样就可以减少代码中大量的重复。
  • 设计模式能带给你的时一些思想,但在平时的开发中怎样清晰的提炼出符合此思路的建造模块,是比较难的,往往时倒逼的,复杂的业务频繁的变化,不断的挑战。
0

评论区