无论承接什么样的需求,是不是身边总有那么几个人代码写的烂,但是却时常有测试小姐姐过来聊天( 求 改bug)、有产品小伙伴送吃的( 求写需求 )、有业务小妹妹陪着改代码( 求上线 ),直至领导都认为他的工作 很重要,而在旁边的你只能蹭点吃的。
这样的小伙伴,可能把代码写的很直接, ifelse 多用一点,满足于先临时支持一下,想着这也没什么 的。而且这样的业务需求要的急又都是增删改查的内容,实在不想做设计。而如果有人提到说好设计 下,可能也会被反对不要过渡设计。
第一次完成产品需求实在是很快,但互联网的代码不比传统企业。在传统行业可能一套代码能用十年, 但在互联网高速的迭代下你的工程,一年就要变动几十次。如果从一开始就想着只要完成功能就可以, 那么随之而来的是后续的需求难以承接,每次看着成片成片的代码,实在不知如何下手。
一个项目的上线往要经历业务需求 、产品设计 、研发实现 、测试验证 、上线部署到正式开量 ,而这 其中对研发非常重要的一点就是研发实现的过程,又可以包括为;架构选型 、功能设计 、设计评审 、 代码实现 、代码评审 、单测覆盖率检查 、编写⽂文档 、提交测试 。所以在一些流程规范下,其实很难让 你随意开发代码。 开发代码的过程不是炫技 ,就像盖房子如果不按照图纸来修建,回首就在山墙上搭一个厨房卫浴!可能 在现实场景中这很荒唐,但在功能开发中却总有这样的代码。 所以我们也需要一些设计模式的标准思想,去建设代码结构,提升全局把控能⼒力。
所以我们也需要一些设计模式的标准思想,去建设代码结构,提升全局把控能力。

一、开发环境

  1. JDK 1.8
    2. Idea + Maven

    二、建造者模式介绍

    1631616420(1).png
    例如你玩王者荣耀的时的初始化界面;有三条路、有树木、有野怪、有守卫塔等等,甚至依赖于你的网 络情况会控制清晰度。而当你换一个场景进行其他不同模式的选择时,同样会建设道路、树木、野怪等 等,但是他们的摆放和大小都有不同。这里就可以用到建造者模式来初始化游戏元素。 而这样的根据相同的物料 ,不同的组装所产生出的具体的内容,就是建造者模式的最终意图,也就是将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。

    三、案例场景模拟

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

    1. 场景模拟工程

    1631617532(1).png
    在模拟工程中提供了装修中所需要的物料; ceilling(吊顶) 、 coat(涂料) 、 floor(地板) 、 tile(地砖) ,这么四项内容。( 实际的装修物料要比这个多的多 )

    2. 场景简述

    2.1 物料接口

    ```java public interface Matter {

    /**

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

      /**

    • 品牌 */ String brand();

      /**

    • 型号 */ String model();

      /**

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

      /**

    • 描述 */ String desc();

}

  1. 物料接口提供了基本的信息,以保证所有的装修材料都可以按照统一标准进行获取。
  2. <a name="Qt6bR"></a>
  3. ### 2.2 吊顶(ceiling)
  4. **一级顶**
  5. ```java
  6. public class LevelOneCeiling implements Matter {
  7. @Override
  8. public String scene() {
  9. return "吊顶";
  10. }
  11. @Override
  12. public String brand() {
  13. return "装修公司自带";
  14. }
  15. @Override
  16. public String model() {
  17. return "一级顶";
  18. }
  19. @Override
  20. public BigDecimal price() {
  21. return new BigDecimal(260);
  22. }
  23. @Override
  24. public String desc() {
  25. return "造型只做低一级,只有一个层次的吊顶,一般离顶120-150mm";
  26. }
  27. }

二级顶

  1. public class LevelTwoCeiling implements Matter {
  2. @Override
  3. public String scene() {
  4. return "吊顶";
  5. }
  6. @Override
  7. public String brand() {
  8. return "装修公司自带";
  9. }
  10. @Override
  11. public String model() {
  12. return "二级顶";
  13. }
  14. @Override
  15. public BigDecimal price() {
  16. return new BigDecimal(850);
  17. }
  18. @Override
  19. public String desc() {
  20. return "两个层次的吊顶,二级吊顶高度一般就往下吊20cm,要是层高很高,也可增加每级的厚度";
  21. }
  22. }

2.3 涂料(coat)

多乐士

  1. public class DuluxCoat implements Matter {
  2. @Override
  3. public String scene() {
  4. return "涂料";
  5. }
  6. @Override
  7. public String brand() {
  8. return "多乐士(Dulux)";
  9. }
  10. @Override
  11. public String model() {
  12. return "第二代";
  13. }
  14. @Override
  15. public BigDecimal price() {
  16. return new BigDecimal(719);
  17. }
  18. @Override
  19. public String desc() {
  20. return "多乐士是阿克苏诺贝尔旗下的著名建筑装饰油漆品牌,产品畅销于全球100个国家,每年全球有5000万户家庭使用多乐士油漆。";
  21. }
  22. }

立邦

  1. public class LiBangCoat implements Matter {
  2. @Override
  3. public String scene() {
  4. return "涂料";
  5. }
  6. @Override
  7. public String brand() {
  8. return "立邦";
  9. }
  10. @Override
  11. public String model() {
  12. return "默认级别";
  13. }
  14. @Override
  15. public BigDecimal price() {
  16. return new BigDecimal(650);
  17. }
  18. @Override
  19. public String desc() {
  20. return "立邦始终以开发绿色产品、注重高科技、高品质为目标,以技术力量不断推进科研和开发,满足消费者需求。";
  21. }
  22. }

2.4 地板(floor)

德尔

  1. public class DerFloor implements Matter {
  2. @Override
  3. public String scene() {
  4. return "地板";
  5. }
  6. @Override
  7. public String brand() {
  8. return "德尔(Der)";
  9. }
  10. @Override
  11. public String model() {
  12. return "A+";
  13. }
  14. @Override
  15. public BigDecimal price() {
  16. return new BigDecimal(119);
  17. }
  18. @Override
  19. public String desc() {
  20. return "DER德尔集团是全球领先的专业木地板制造商,北京2008年奥运会家装和公装地板供应商";
  21. }
  22. }

圣象

  1. public class ShengXiangFloor implements Matter {
  2. @Override
  3. public String scene() {
  4. return "地板";
  5. }
  6. @Override
  7. public String brand() {
  8. return "圣象";
  9. }
  10. @Override
  11. public String model() {
  12. return "一级";
  13. }
  14. @Override
  15. public BigDecimal price() {
  16. return new BigDecimal(318);
  17. }
  18. @Override
  19. public String desc() {
  20. return "圣象地板是中国地板行业著名品牌。圣象地板拥有中国驰名商标、中国名牌、国家免检、中国环境标志认证等多项荣誉。";
  21. }
  22. }

2.5 地砖(tile)

东鹏

  1. public class DongPengTile implements Matter {
  2. @Override
  3. public String scene() {
  4. return "地砖";
  5. }
  6. @Override
  7. public String brand() {
  8. return "东鹏瓷砖";
  9. }
  10. @Override
  11. public String model() {
  12. return "10001";
  13. }
  14. @Override
  15. public BigDecimal price() {
  16. return new BigDecimal(102);
  17. }
  18. @Override
  19. public String desc() {
  20. return "东鹏瓷砖以品质铸就品牌,科技推动品牌,口碑传播品牌为宗旨,2014年品牌价值132.35亿元,位列建陶行业榜首。";
  21. }
  22. }

马可波罗

  1. public class MarcoPoloTile implements Matter {
  2. @Override
  3. public String scene() {
  4. return "地砖";
  5. }
  6. @Override
  7. public String brand() {
  8. return "马可波罗(MARCO POLO)";
  9. }
  10. @Override
  11. public String model() {
  12. return "缺省";
  13. }
  14. @Override
  15. public BigDecimal price() {
  16. return new BigDecimal(140);
  17. }
  18. @Override
  19. public String desc() {
  20. return "“马可波罗”品牌诞生于1996年,作为国内最早品牌化的建陶品牌,以“文化陶瓷”占领市场,享有“仿古砖至尊”的美誉。";
  21. }
  22. }

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

四、用一坨坨代码实现

每一个章节中我们都会使用这样很直白的方式去把功能实现出来,在通过设计模式去优化完善。这样的 代码结构也都是非常简单的,没有复杂的类关系结构,都是直来直去的代码。除了我们经常强调的这样 的代码不能很好的扩展外,做一些例子demo工程还是可以的。

1. 工程结构

1631618786(1).png
一个类几千行的代码你是否见过,嚯?那今天就让你见识一下有这样潜质的类!

2. ifelse实现需求

  1. public class DecorationPackageController {
  2. public String getMatterList(BigDecimal area, Integer level) {
  3. List<Matter> list = new ArrayList<Matter>(); // 装修清单
  4. BigDecimal price = BigDecimal.ZERO; // 装修价格
  5. // 豪华欧式
  6. if (1 == level) {
  7. LevelTwoCeiling levelTwoCeiling = new LevelTwoCeiling(); // 吊顶,二级顶
  8. DuluxCoat duluxCoat = new DuluxCoat(); // 涂料,多乐士
  9. ShengXiangFloor shengXiangFloor = new ShengXiangFloor(); // 地板,圣象
  10. list.add(levelTwoCeiling);
  11. list.add(duluxCoat);
  12. list.add(shengXiangFloor);
  13. price = price.add(area.multiply(new BigDecimal("0.2")).multiply(levelTwoCeiling.price()));
  14. price = price.add(area.multiply(new BigDecimal("1.4")).multiply(duluxCoat.price()));
  15. price = price.add(area.multiply(shengXiangFloor.price()));
  16. }
  17. // 轻奢田园
  18. if (2 == level) {
  19. LevelTwoCeiling levelTwoCeiling = new LevelTwoCeiling(); // 吊顶,二级顶
  20. LiBangCoat liBangCoat = new LiBangCoat(); // 涂料,立邦
  21. MarcoPoloTile marcoPoloTile = new MarcoPoloTile(); // 地砖,马可波罗
  22. list.add(levelTwoCeiling);
  23. list.add(liBangCoat);
  24. list.add(marcoPoloTile);
  25. price = price.add(area.multiply(new BigDecimal("0.2")).multiply(levelTwoCeiling.price()));
  26. price = price.add(area.multiply(new BigDecimal("1.4")).multiply(liBangCoat.price()));
  27. price = price.add(area.multiply(marcoPoloTile.price()));
  28. }
  29. // 现代简约
  30. if (3 == level) {
  31. LevelOneCeiling levelOneCeiling = new LevelOneCeiling(); // 吊顶,二级顶
  32. LiBangCoat liBangCoat = new LiBangCoat(); // 涂料,立邦
  33. DongPengTile dongPengTile = new DongPengTile(); // 地砖,东鹏
  34. list.add(levelOneCeiling);
  35. list.add(liBangCoat);
  36. list.add(dongPengTile);
  37. price = price.add(area.multiply(new BigDecimal("0.2")).multiply(levelOneCeiling.price()));
  38. price = price.add(area.multiply(new BigDecimal("1.4")).multiply(liBangCoat.price()));
  39. price = price.add(area.multiply(dongPengTile.price()));
  40. }
  41. StringBuilder detail = new StringBuilder("\r\n-------------------------------------------------------\r\n" +
  42. "装修清单" + "\r\n" +
  43. "套餐等级:" + level + "\r\n" +
  44. "套餐价格:" + price.setScale(2, BigDecimal.ROUND_HALF_UP) + " 元\r\n" +
  45. "房屋面积:" + area.doubleValue() + " 平米\r\n" +
  46. "材料清单:\r\n");
  47. for (Matter matter: list) {
  48. detail.append(matter.scene()).append(":").append(matter.brand()).append("、").append(matter.model()).append("、平米价格:").append(matter.price()).append(" 元。\n");
  49. }
  50. return detail.toString();
  51. }
  52. }

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

3. 测试验证

接下来我们通过junit单元测试的方式验证接口服务,强调日常编写好单测可以更好的提高系统的健壮 度。
编写测试类:

  1. public class ApiTest {
  2. @Test
  3. public void test_DecorationPackageController(){
  4. DecorationPackageController decoration = new DecorationPackageController();
  5. // 豪华欧式
  6. System.out.println(decoration.getMatterList(new BigDecimal("132.52"),1));
  7. // 轻奢田园
  8. System.out.println(decoration.getMatterList(new BigDecimal("98.25"),2));
  9. // 现代简约
  10. System.out.println(decoration.getMatterList(new BigDecimal("85.43"),3));
  11. }
  12. }

结果:
1631619018(1).png
看到输出的这个结果,已经很有装修公司提供报价单的感觉了。以上这段使用 ifelse 方式实现的 代码,目前已经满足的我们的也许功能。但随着老板对业务的快速发展要求,会提供很多的套餐针 对不同的户型。那么这段实现代码将迅速扩增到几千行,甚至在修修改改中,已经像膏药一样难以 维护。

五、建造者模式重构代码

接下来使用建造者模式来进行代码优化,也算是一次很小的重构。
建造者模式主要解决的问题是在软件系统中,有时候面临着”一个复杂对象”的创建工作,其通常由各个 部分的子对象用一定的过程构成;由于需求的变化,这个复杂对象的各个部分经常面临着重大的变化, 但是将它们组合在一起的过程却相对稳定。 这里我们会把构建的过程交给创建者类,而创建者通过使用我们的构建工具包 ,去构建出不同的 装修套 餐 。

1. 工程结构

image.png
建造者模型结构
image.png
工程中有三个核心类和一个测试类,核心类是建造者模式的具体实现。与 ifelse 实现⽅方式相比,多出 来了两个二外的类。具体功能如下;
Builder ,建造者类具体的各种组装由此类实现。
DecorationPackageMenu ,是 IMenu 接口的实现类,主要是承载建造过程中的填充器。相当于 这是一套承载物料和创建者中间衔接的内容。
接下来会分别讲解几个类的具体实现。

2. 代码实现

2.1 定义装修包接口

  1. public interface IMenu {
  2. /**
  3. * 吊顶
  4. */
  5. IMenu appendCeiling(Matter matter);
  6. /**
  7. * 涂料
  8. */
  9. IMenu appendCoat(Matter matter);
  10. /**
  11. * 地板
  12. */
  13. IMenu appendFloor(Matter matter);
  14. /**
  15. * 地砖
  16. */
  17. IMenu appendTile(Matter matter);
  18. /**
  19. * 明细
  20. */
  21. String getDetail();
  22. }

接口类中定义了填充各项物料的方法; 吊顶 、 涂料 、 地板 、 地砖 ,以及最终提供获取全部明细 的方法。

2.2 装修包实现

  1. public class DecorationPackageMenu implements IMenu {
  2. private List<Matter> list = new ArrayList<Matter>(); // 装修清单
  3. private BigDecimal price = BigDecimal.ZERO; // 装修价格
  4. private BigDecimal area; // 面积
  5. private String grade; // 装修等级;豪华欧式、轻奢田园、现代简约
  6. private DecorationPackageMenu() {
  7. }
  8. public DecorationPackageMenu(Double area, String grade) {
  9. this.area = new BigDecimal(area);
  10. this.grade = grade;
  11. }
  12. @Override
  13. public IMenu appendCeiling(Matter matter) {
  14. list.add(matter);
  15. price = price.add(area.multiply(new BigDecimal("0.2")).multiply(matter.price()));
  16. return this;
  17. }
  18. @Override
  19. public IMenu appendCoat(Matter matter) {
  20. list.add(matter);
  21. price = price.add(area.multiply(new BigDecimal("1.4")).multiply(matter.price()));
  22. return this;
  23. }
  24. @Override
  25. public IMenu appendFloor(Matter matter) {
  26. list.add(matter);
  27. price = price.add(area.multiply(matter.price()));
  28. return this;
  29. }
  30. @Override
  31. public IMenu appendTile(Matter matter) {
  32. list.add(matter);
  33. price = price.add(area.multiply(matter.price()));
  34. return this;
  35. }
  36. @Override
  37. public String getDetail() {
  38. StringBuilder detail = new StringBuilder("\r\n-------------------------------------------------------\r\n" +
  39. "装修清单" + "\r\n" +
  40. "套餐等级:" + grade + "\r\n" +
  41. "套餐价格:" + price.setScale(2, BigDecimal.ROUND_HALF_UP) + " 元\r\n" +
  42. "房屋面积:" + area.doubleValue() + " 平米\r\n" +
  43. "材料清单:\r\n");
  44. for (Matter matter: list) {
  45. detail.append(matter.scene()).append(":").append(matter.brand()).append("、").append(matter.model()).append("、平米价格:").append(matter.price()).append(" 元。\n");
  46. }
  47. return detail.toString();
  48. }
  49. }

装修包的实现中每一个方法都会有this ,也就可以非常方便的用于连续填充各项物料。
同时在填充时也会根据物料计算平米数下的报价,吊顶和涂料按照平米数适量乘以常熟计算。
最后同样提供了统一的获取装修清单的明细方法。

2.3 建造者方法

  1. public class Builder {
  2. public IMenu levelOne(Double area) {
  3. return new DecorationPackageMenu(area, "豪华欧式")
  4. .appendCeiling(new LevelTwoCeiling()) // 吊顶,二级顶
  5. .appendCoat(new DuluxCoat()) // 涂料,多乐士
  6. .appendFloor(new ShengXiangFloor()); // 地板,圣象
  7. }
  8. public IMenu levelTwo(Double area){
  9. return new DecorationPackageMenu(area, "轻奢田园")
  10. .appendCeiling(new LevelTwoCeiling()) // 吊顶,二级顶
  11. .appendCoat(new LiBangCoat()) // 涂料,立邦
  12. .appendTile(new MarcoPoloTile()); // 地砖,马可波罗
  13. }
  14. public IMenu levelThree(Double area){
  15. return new DecorationPackageMenu(area, "现代简约")
  16. .appendCeiling(new LevelOneCeiling()) // 吊顶,二级顶
  17. .appendCoat(new LiBangCoat()) // 涂料,立邦
  18. .appendTile(new DongPengTile()); // 地砖,东鹏
  19. }
  20. }

建造者的使用中就已经非常容易了,统一的建造方式,通过不同物料填充出不同的装修风格; 豪华 欧式 、 轻奢田园 、 现代简约 ,如果将来业务扩展也可以将这部分内容配置到数据库自动生成。但 整体的思想还可以使用创建者模式进行搭建。

3. 测试验证

编写测试类:

  1. public class ApiTest {
  2. @Test
  3. public void test_Builder(){
  4. Builder builder = new Builder();
  5. // 豪华欧式
  6. System.out.println(builder.levelOne(132.52D).getDetail());
  7. // 轻奢田园
  8. System.out.println(builder.levelTwo(98.25D).getDetail());
  9. // 现代简约
  10. System.out.println(builder.levelThree(85.43D).getDetail());
  11. }
  12. }

结果:
image.png
测试结果是一样的,调用方式也基本类似。但是目前的代码结构却可以让你很方便的很有调理的进 行扩展业务开发。而不是以往一样把所有代码都写到 ifelse 里面。

六、总结

通过上面对建造者模式的使用,已经可以摸索出一点心得。那就是什么时候会选择这样的设计模 式,当: 一些基本物料不会变,而其组合经常变化的时候 ,就可以选择这样的设计模式来构建代码。
此设计模式满足了⼀一职责原则以及可复用的技术、建造者独立、易扩展、便于控制细节风险。但 同时当出现特别多的物料以及很多的组合后,类的不断扩展也会造成难以维护的问题。但这种设计 结构模型可以把重复的内容抽象到数据库中,按照需要配置。这样就可以减少代码中大量的重复。
设计模式能带给你的是一些思想,但在平时的开发中怎么样清晰的提炼出符合此思路的建造模块, 是比较难的。需要经过一些锻炼和不断承接更多的项目,从而获得这部分经验。有的时候你的代码 写的好,往往是倒逼的,复杂的业务频繁的变化,不断的挑战!