创造者模式:工厂方法、抽象工厂、生成器、原型、单例

这类模式提供创建对象的机制,能够提升已有代码的灵活性和可复用性

第三节、建造者模式

一、建造者模式介绍

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

例如初始化游戏元素的时候,会创建模型,但是他们的摆放以及大小都有不同。这里就可以用到建造者模式来初始化游戏元素。

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

二、案例场景模拟

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

模拟装修公司按照不同的价格设定品牌选择组合,以达到使用建造者模式的过程

1. 场景模拟工程

在模拟工程中提供了装修所需要的物料;ceiling(吊顶)、coat(涂料)、floor(地板)、tile(地板)。

2. 场景简述

2.1 物料接口
package org.levtio.demo.design;

import java.math.BigDecimal;

/**
* @Author: LuoMingDong
* @Date: 2023/9/19 10:22
* @Describe: 物料接口,提供所有基本信息
*/
public interface Matter {

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

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

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

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

/**
* 描述
*/
String desc();
}
2.2 吊顶

一级顶

package org.levtio.demo.design.ceiling;

import org.levtio.demo.design.Matter;

import java.math.BigDecimal;

/**
* @Author: LuoMingDong
* @Date: 2023/9/19 10:34
* @Describe:
* 吊顶
* 品牌;装修公司自带
* 型号:一级顶
*/
public class LevelOneCeiling 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 "造型只做第一级,只有一个层次的吊顶,一般离顶120-150mm";
}
}

二级顶

package org.levtio.demo.design.ceiling;

import org.levtio.demo.design.Matter;

import java.math.BigDecimal;

/**
* @Author: LuoMingDong
* @Date: 2023/9/19 10:41
* @Describe:
* 吊顶
* 品牌;装修公司自带
* 型号:二级顶
*/
public class LevelTwoCeiling 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 "两个层次的吊顶,二级吊顶高度一般就往下吊20cm,要是层高很高,也可增加每级的厚度";
}
}
2.3 涂料

多乐士

package org.levtio.demo.design.coat;

import org.levtio.demo.design.Matter;

import java.math.BigDecimal;

/**
* @Author: LuoMingDong
* @Date: 2023/9/19 10:48
* @Describe:
* 涂料
* 品牌;多乐士(Dulux)
*/
public class DuluxCoat implements Matter {
@Override
public String scene() {
return "涂料";
}

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

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

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

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

立邦

package org.levtio.demo.design.coat;

import org.levtio.demo.design.Matter;

import java.math.BigDecimal;

/**
* @Author: LuoMingDong
* @Date: 2023/9/19 10:50
* @Describe:
* 涂料
* 品牌;立邦
*/
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 "立邦始终以开发绿色产品、注重高科技、高品质为目标,以技术力量不断推进科研和开发,满足消费者需求。";
}
}
2.4 地板

德尔

package org.levtio.demo.design.floor;

import org.levtio.demo.design.Matter;

import java.math.BigDecimal;

/**
* @Author: LuoMingDong
* @Date: 2023/9/19 10:52
* @Describe:
*/
public class DerFloor implements Matter {
@Override
public String scene() {
return null;
}

@Override
public String brand() {
return null;
}

@Override
public String model() {
return null;
}

@Override
public BigDecimal price() {
return null;
}

@Override
public String desc() {
return null;
}
}

圣象

package org.levtio.demo.design.floor;

import org.levtio.demo.design.Matter;

import java.math.BigDecimal;

/**
* @Author: LuoMingDong
* @Date: 2023/9/19 10:54
* @Describe:
*/
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 "圣象地板是中国地板行业著名品牌。圣象地板拥有中国驰名商标、中国名牌、国家免检、中国环境标志认证等多项荣誉。";
}
}
2.5 地砖

东鹏

package org.levtio.demo.design.tile;

import org.levtio.demo.design.Matter;

import java.math.BigDecimal;

/**
* @Author: LuoMingDong
* @Date: 2023/9/19 10:57
* @Describe:
*/
public class DongPengTile implements Matter {
@Override
public String scene() {
return "地砖";
}

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

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

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

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

马可波罗

package org.levtio.demo.design.tile;

import org.levtio.demo.design.Matter;

import java.math.BigDecimal;

/**
* @Author: LuoMingDong
* @Date: 2023/9/19 10:57
* @Describe:
*/
public class MarcoPoloTile implements Matter {
@Override
public String scene() {
return "地砖";
}

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

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

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

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

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

三、ifelse代码实现

1.ifelse工程结构

2.通过ifelse实现需求

package org.levtio.demo.design;

import org.levtio.demo.design.ceiling.LevelOneCeiling;
import org.levtio.demo.design.ceiling.LevelTwoCeiling;
import org.levtio.demo.design.coat.DuluxCoat;
import org.levtio.demo.design.coat.LiBangCoat;
import org.levtio.demo.design.floor.ShengXiangFloor;
import org.levtio.demo.design.tile.DongPengTile;
import org.levtio.demo.design.tile.MarcoPoloTile;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

/**
* @Author: LuoMingDong
* @Date: 2023/9/19 11:02
* @Describe:
*/
public class DecorationPackageController {
public String getMatterList(BigDecimal area, Integer level){
List<Matter> list = new ArrayList<>(); //装修清单
BigDecimal price = BigDecimal.ZERO; //装修价格
//豪华模式
if (level == 1){
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 (level == 2) {
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 (level == 3){
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();
}
}

代码分析:

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

3.测试验证

测试类代码:

package org.levtio.demo.design.test;

import org.junit.Test;
import org.levtio.demo.design.DecorationPackageController;

import java.math.BigDecimal;

/**
* @Author: LuoMingDong
* @Date: 2023/9/19 11:17
* @Describe:
*/
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));
}
}

测试结果:

-------------------------------------------------------
装修清单
套餐等级: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 元。

四、建造者模式重构代码

1.建造者模式工程结构

建造者模式结构

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

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

2.代码实现

2.1 定义装修包接口
package org.levtio.demo.design;

/**
* @Author: LuoMingDong
* @Date: 2023/9/19 11:39
* @Describe:
*/
public interface IMenu {
/**
* 吊顶
*/
IMenu appendCeiling(Matter matter);

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

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

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

/**
* 明细
*/
String getDetail();
}
2.2 装修包实现
package org.levtio.demo.design;

import org.levtio.demo.design.coat.LiBangCoat;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

/**
* @Author: LuoMingDong
* @Date: 2023/9/19 11:40
* @Describe:
*/
public class DecorationPackageMenu implements IMenu{
private List<Matter> list = new ArrayList<>();
private BigDecimal price = BigDecimal.ZERO;

private BigDecimal area;
private String grade;

public DecorationPackageMenu() {
}

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

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

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

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

@Override
public IMenu 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();
}
}
  1. 装修包的实现中每⼀个⽅法都返回了 this ,也就可以⾮常⽅便的⽤于连续填充各项物料。
  2. 同时在填充时也会根据物料计算平⽶数下的报价,吊顶和涂料按照平⽶数适量乘以常数计算。
  3. 最后同样提供了统⼀的获取装修清单的明细⽅法。
2.3 建造者方法
package org.levtio.demo.design;

import org.levtio.demo.design.ceiling.LevelOneCeiling;
import org.levtio.demo.design.ceiling.LevelTwoCeiling;
import org.levtio.demo.design.coat.DuluxCoat;
import org.levtio.demo.design.coat.LiBangCoat;
import org.levtio.demo.design.floor.ShengXiangFloor;
import org.levtio.demo.design.tile.DongPengTile;
import org.levtio.demo.design.tile.MarcoPoloTile;

/**
* @Author: LuoMingDong
* @Date: 2023/9/19 11:48
* @Describe:
*/
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()); // 地砖,东鹏
}
}

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

3.测试验证

编写测试类
package org.levtio.demo.design.test;

import org.junit.Test;
import org.levtio.demo.design.Builder;

/**
* @Author: LuoMingDong
* @Date: 2023/9/19 11:50
* @Describe:
*/
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());
}
}

结果
-------------------------------------------------------
装修清单
套餐等级:豪华欧式
套餐价格: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 ⾥⾯。

五、总结

  1. 通过上面对建造者模式的使用,已经可以摸索出一点心得。那就是为什么时候会选择这样的设计模式:一些基本物料不会变,而其组合经常变化的时候,就可以选择这样的设计模式来构建代码。
  2. 此设计模式满足了单一职责原则以及可复用的技术、建造者独立、易扩展、便于控制细节风险。但同时当出现特别多的物料以及很多的组合后,类的不断扩展也会造成难以维护的问题。但这种设计结构模型可以把重复的内容抽象到数据库中,按照需要配置。这样就可以减少代码中⼤量的重复。