文章目录
前言
最近接手的项目遇到了比较大的坑,在实现流量套餐业务时(有贵宾、高级贵宾、WiFi、导航等等套餐),在其实现的code中光if…else…就超过了100行以上,此时心里一万只羊驼跑过…,最近刚好在学习设计模式,这类的业务可以用建造者模式设计,便先写下这篇作者对建造者模式理解,希望能帮到你。
为什么学习设计模式?
- 领域建模能力 将复杂业务合理拆分;
- 提升自身功能设计和code能力;
- code可读性强,扩展性强;
- 提升阅读框架源码能力;
- 面试加分项。
设计模式分类
- 创建型模式:提供创建对象的机制,提升已有代码的灵活性和可复用性;
- 结构型模式:介绍如何将对象和类组装成较大的结构,并同时保持结构的灵活和高效;
- 行为型模式:负载对象间的高效沟通和职责传递委派。
本篇介绍的建造者模式就是属于 创建型模式。
需求模拟
为了让建造者模式有温度,这里模拟一个业务需求,通过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)模式的主要角色如下:
- 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
- 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
- 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
- 指挥者(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设计模式》这本书,在使用建造者模式重构该业务时并没有按照书上方式实现(书上实现方式有简化),而是通过自己的理解严格按照建造者模式的结构实现。
通过上面对建造者模式的使用可以总结出:
- 建造者模式选择条件:当一些基本材料不变,而组合经常变化(如套餐);
- 建造者模式满足了单一职责原则及可复用技术,建造者独立,易扩展、便于扩展细节风险。
都看到最后了,请帮忙一键三连哦!