页首声明:此篇来源 作者:小傅哥 博客:https://bugstack.cn 公众号:bugstack虫洞栈
建造者模式
建造者模式所完成的内容就是通过将多个简单对象通过一步步的组装构建出一个复杂对象的过程。
那么,哪里有这样的场景呢?
例如你玩王者荣耀的时的初始化界面;有三条路、有树木、有野怪、有守卫塔等等,甚至依赖于你的网络情况会控制清晰度。而当你换一个场景进行其他不同模式的选择时,同样会建设道路、树木、野怪等等,但是他们的摆放和大小都有不同。这里就可以用到建造者模式来初始化游戏元素。
而这样的根据相同的物料,不同的组装所产生出的具体的内容,就是建造者模式的最终意图,也就是;将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
这里我们模拟装修公司对于设计出一些套餐装修服务的场景。
很多装修公司都会给出自家的套餐服务,一般有;欧式豪华、轻奢田园、现代简约等等,而这些套餐的后面是不同的商品的组合。例如;一级&二级吊顶、多乐士涂料、圣象地板、马可波罗地砖等等,按照不同的套餐的价格选取不同的品牌组合,最终再按照装修面积给出一个整体的报价。
这里我们就模拟装修公司想推出一些套餐装修服务,按照不同的价格设定品牌选择组合,以达到使用建造者模式的过程。
案例模拟
itstack-demo-design-3-00
└── src
└── main
└── java
└── org.itstack.demo.design
├── ceilling
│ ├── LevelOneCeiling.java
│ └── LevelTwoCeiling.java
├── coat
│ ├── DuluxCoat.java
│ └── LiBangCoat.java
│ └── LevelTwoCeiling.java
├── floor
│ ├── DerFloor.java
│ └── ShengXiangFloor.java
├── tile
│ ├── DongPengTile.java
│ └── MarcoPoloTile.java
└── Matter.java
在模拟工程中提供了装修中所需要的物料;ceilling(吊顶)、coat(涂料)、floor(地板)、tile(地砖),这么四项内容。(实际的装修物料要比这个多的多)
物料接口
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,要是层高很高,也可增加每级的厚度";
}
}
涂料(coat)
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 "立邦始终以开发绿色产品、注重高科技、高品质为目标,以技术力量不断推进科研和开发,满足消费者需求。";
}
}
地板(floor)
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 "圣象地板是中国地板行业著名品牌。圣象地板拥有中国驰名商标、中国名牌、国家免检、中国环境标志认证等多项荣誉。";
}
}
地砖(tile)
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亿元,位列建陶行业榜首。";
}
}
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年,作为国内最早品牌化的建陶品牌,以“文化陶瓷”占领市场,享有“仿古砖至尊”的美誉。";
}
}
以上就是本次装修公司所提供的装修配置单,接下我们会通过案例去使用不同的物料组合出不同的套餐服务。
用糟糕的代码实现
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();
}
}
随着老板对业务的快速发展要求,会提供很多的套餐针对不同的户型。那么这段实现代码将迅速扩增到几千行,甚至在修修改改中,已经像膏药一样难以维护。
建造者模式介绍
**意图:**将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
**主要解决:**主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
**何时使用:**一些基本部件不会变,而其组合经常变化的时候。
**如何解决:**将变与不变分离开。
**关键代码:**建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。
应用实例: 1、去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"。 2、JAVA 中的 StringBuilder。
优点: 1、建造者独立,易扩展。 2、便于控制细节风险。
缺点: 1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类。
使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。
**注意事项:**与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。
使用建造者模式
itstack-demo-design-3-02
└── src
├── main
│ └── java
│ └── org.itstack.demo.design
│ ├── Builder.java
│ ├── DecorationPackageMenu.java
│ └── IMenu.java
└── test
└── java
└── org.itstack.demo.design.test
└── ApiTest.java
工程中有三个核心类和一个测试类,核心类是建造者模式的具体实现。与ifelse实现方式相比,多出来了两个二外的类。具体功能如下;
Builder,建造者类具体的各种组装由此类实现。DecorationPackageMenu,是IMenu接口的实现类,主要是承载建造过程中的填充器。相当于这是一套承载物料和创建者中间衔接的内容。
好,那么接下来会分别讲解几个类的具体实现。
public interface IMenu {
IMenu
appendCeiling(Matter matter
);
IMenu
appendCoat(Matter matter
);
IMenu
appendFloor(Matter matter
);
IMenu
appendTile(Matter matter
);
String
getDetail();
}
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,也就可以非常方便的用于连续填充各项物料。同时在填充时也会根据物料计算平米数下的报价,吊顶和涂料按照平米数适量乘以常数计算。最后同样提供了统一的获取装修清单的明细方法。
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());
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4mIeRnfi-1603252044293)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201015170022649.png)]
总结
通过上面对建造者模式的使用,已经可以摸索出一点心得。那就是什么时候会选择这样的设计模式,当:一些基本物料不会变,而其组合经常变化的时候,就可以选择这样的设计模式来构建代码。此设计模式满足了单一职责原则以及可复用的技术、建造者独立、易扩展、便于控制细节风险。但同时当出现特别多的物料以及很多的组合后,类的不断扩展也会造成难以维护的问题。但这种设计结构模型可以把重复的内容抽象到数据库中,按照需要配置。这样就可以减少代码中大量的重复。 velThree(Double area){ return new DecorationPackageMenu(area, “现代简约”) .appendCeiling(new LevelOneCeiling()) // 吊顶,二级顶 .appendCoat(new LiBangCoat()) // 涂料,立邦 .appendTile(new DongPengTile()); // 地砖,东鹏 }
}
[外链图片转存中...(img-4mIeRnfi-1603252044293)]
### 总结
- 通过上面对建造者模式的使用,已经可以摸索出一点心得。那就是什么时候会选择这样的设计模式,当:`一些基本物料不会变,而其组合经常变化的时候`,就可以选择这样的设计模式来构建代码。
- 此设计模式满足了单一职责原则以及可复用的技术、建造者独立、易扩展、便于控制细节风险。但同时当出现特别多的物料以及很多的组合后,类的不断扩展也会造成难以维护的问题。但这种设计结构模型可以把重复的内容抽象到数据库中,按照需要配置。这样就可以减少代码中大量的重复。
- 设计模式能带给你的是一些思想,但在平时的开发中怎么样清晰的提炼出符合此思路的建造模块,是比较难的。需要经过一些锻炼和不断承接更多的项目,从而获得这部分经验。有的时候你的代码写的好,往往是倒逼的,复杂的业务频繁的变化,不断的挑战!