引子
话说小帅在华山脚下的仙草药房兢兢业业工作了多年,从一个打杂的升级成了一个工厂的负责人,负责生产药房的镇店之宝----超级黑玉断续膏。
超级黑玉断续膏是稀世秘药,有再造之力,神奇无比,其价堪比黄金,服用后立即恢复生命2500点,且具备超级功效,能够同时恢复1000点内力。
此药如此神奇,其炼制程序极其复杂,而且品质很难把控,厂里炼药的师傅虽然经验丰富,但是总不免有所遗漏,导致产品功效时好时坏,客户对此多有怨言。师傅下令小帅三月之内改造流程工艺,杜绝此类问题。
小帅临危受命,立刻展开工作,把超级黑玉断续膏的所有工序流程都整理了一遍:
- 将各种名贵药材按比例调配
- 用华山脚下的山泉浸泡七天七夜
- 泡完之后加入秘药混合均匀
- 在八卦炉中炼制七七四十九天
- 在华山之巅晾晒十天,吸收日月之精华
- 装入特质木盒密封
由于工序流程很复杂,小帅把生产流程做成了一个清单,大家都按照这个清单操作,不管是经验丰富的老师傅,还是新带的徒弟,都严格按照清单上的步骤制作,就不会有差错了。
至于这个清单该如何用于生产呢?小帅以前是个程序员,就用代码把整个过程写了出来,这里还用到一个设计模式,我们一起来看看吧。
建造模式
造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
(图片来源:https://design-patterns.readthedocs.io/zh_CN/latest/creational_patterns/builder.html)
建造者模式通常有两种应用场景:
- 对象创建的步骤比较复杂,需要经过步骤一、二、三等多个步骤才能创建成功
- 对象本身有很多属性,属性之间可能还有制约关系
我们先来看第一种创建复杂对象的情况。
制作黑玉断续膏
制作黑玉断续膏的代码如下:
药品类:
代码语言:javascript复制/**
* 药品类
*/
public class Drug {
/**
* 药品名称
*/
private String name;
/**
* 是否已混合
*/
private boolean isMixed;
/**
* 是否已浸泡
*/
private boolean isSoakd;
/**
* 是否已加入秘药
*/
private boolean hasSecretMedicine;
/**
* 是否已炼制
*/
private boolean isRefine;
/**
* 是否已晾晒
*/
private boolean isDry;
/**
* 是否已密封
*/
private boolean isSeal;
public Drug(String name) {
this.name = name;
}
@Override
public String toString() {
StringBuffer display = new StringBuffer();
display.append("---- 获得 " name " 生产情况 ----n");
display.append("是否已混合:" isMixed "n");
display.append("是否已浸泡:" isSoakd "n");
display.append("是否已加入秘药:" hasSecretMedicine "n");
display.append("是否已炼制:" isRefine "n");
display.append("是否已晾晒:" isDry "n");
display.append("是否已密封:" isSeal "n");
return display.toString();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isMixed() {
return isMixed;
}
public void setMixed(boolean mixed) {
isMixed = mixed;
}
public boolean isSoakd() {
return isSoakd;
}
public void setSoakd(boolean soakd) {
isSoakd = soakd;
}
public boolean isHasSecretMedicine() {
return hasSecretMedicine;
}
public void setHasSecretMedicine(boolean hasSecretMedicine) {
this.hasSecretMedicine = hasSecretMedicine;
}
public boolean isRefine() {
return isRefine;
}
public void setRefine(boolean refine) {
isRefine = refine;
}
public boolean isDry() {
return isDry;
}
public void setDry(boolean dry) {
isDry = dry;
}
public boolean isSeal() {
return isSeal;
}
public void setSeal(boolean seal) {
isSeal = seal;
}
}
药品建造接口:
代码语言:javascript复制/**
* 药品建造接口
*/
public interface DrugBuilder {
/**
* 混合
*/
public void mix();
/**
* 浸泡
*/
public void soak();
/**
* 加入秘药
*/
public void addSecretMedicine();
/**
* 炼制
*/
public void refine();
/**
* 晾晒
*/
public void dry();
/**
* 密封
*/
public void seal();
}
黑玉断续膏建造者类:
代码语言:javascript复制/**
* 黑玉断续膏建造者类
*/
public class HeiYuDuanXuGaoBulider implements DrugBuilder{
private Drug drug;
public HeiYuDuanXuGaoBulider() {
this.drug = new Drug("黑玉断续膏");
}
/**
* 混合
*/
@Override
public void mix() {
this.drug.setMixed(true);
}
/**
* 浸泡
*/
@Override
public void soak() {
this.drug.setSoakd(true);
}
/**
* 加入秘药
*/
@Override
public void addSecretMedicine() {
this.drug.setHasSecretMedicine(true);
}
/**
* 炼制
*/
@Override
public void refine() {
this.drug.setRefine(true);
}
/**
* 晾晒
*/
@Override
public void dry() {
this.drug.setDry(true);
}
/**
* 密封
*/
@Override
public void seal() {
this.drug.setSeal(true);
}
/**
* 获取药品
* @return
*/
public Drug getResult() {
return this.drug;
}
}
导向器类:
代码语言:javascript复制/**
* 导向器类
*/
public class Director {
/**
* 建造药品
* @param drugBuilder
*/
public void constructDrug(DrugBuilder drugBuilder) {
// 混合
drugBuilder.mix();
// 浸泡
drugBuilder.soak();
// 加入秘药
drugBuilder.addSecretMedicine();
// 炼制
drugBuilder.refine();
// 晾晒
drugBuilder.dry();
// 密封
drugBuilder.seal();
}
}
客户端类:
代码语言:javascript复制/**
* 客户端类
*/
public class Demo {
public static void main(String[] args) {
// 创建导向器类
Director director = new Director();
// 创建黑玉断续膏建造者类
HeiYuDuanXuGaoBulider heiYuDuanXuGaoBulider = new HeiYuDuanXuGaoBulider();
// 建造药品
director.constructDrug(heiYuDuanXuGaoBulider);
// 获取药品
Drug drug = heiYuDuanXuGaoBulider.getResult();
System.out.println(drug);
}
}
运行结果:
代码语言:javascript复制---- 获得 黑玉断续膏 生产情况 ----
是否已混合:true
是否已浸泡:true
是否已加入秘药:true
是否已炼制:true
是否已晾晒:true
是否已密封:true
DrugBuilder:定义为了创建一个Drug对象所需要的方法步骤。
HeiYuDuanXuGaoBulider:实现DrugBuilder的接口,根据黑玉断续膏的特性实现接口方法。
Director:指导类,构造使用DrugBuilder接口的对象,按照接口里定义的步骤,一步步地操作,生产药品。
Drug:表示要被构造的复杂对象即黑玉断续膏。
客户端只要调用Director类的constructDrug方法来创建对象就好了,具体的生产步骤和流程都由Director类来控制,对客户端来说都是透明的,大大减轻了客户端的负担。
多个参数
最近,有些武林人士反映,他们有时候备了治疗内伤的药,却受了外伤,内伤的药用不起来,过了保质期就白白浪费掉了;
如果备了治疗外伤的药,有时候又受内伤,也会造成浪费;
如果同时备治疗内伤和外伤的药,则会大幅增加成本(现在混江湖也不容易),而且一般情况下不会即受内伤又受外伤(那样也太惨了吧)。
为了提高服务质量,满足客户个性化的需求,小帅的师父最近研发了一款新品,客户可以在现场调配出治疗内伤或外伤药品的便携炼丹炉。
原理是这样的,治疗内伤的药和治疗外伤的药其实大部分成分是一样的,只有一小部分不一样,治疗内伤的药添加的是仙灵草,治疗外伤的药添加的是大力丸。
所以只需要把仙灵草和大力丸磨成粉末,装成小包,给客户带在身上,根据需要添加仙灵草或大力丸,然后用我们生产的特制混合壶混合就行了。
为了满足个性化需求,我们还为客户提供了甜和咸两种口味哦!
说了这么多,我们来看看代码吧:
药品类:
代码语言:javascript复制/**
* 药品类
*/
public class Drug {
/**
* 药品名称
*/
private String name;
/**
* 治疗类型(0:治疗内伤;1:治疗外伤)
*/
private int type;
/**
* 口味(1:甜味;2:咸味)
*/
private int taste;
/**
* 药品颗粒尺寸(单位cm)
*/
private int size;
/**
* 数量
*/
private int num;
/**
* 构造方法,通过Builder类的属性来构造对象
* @param drugBuilder
*/
public Drug(DrugBuilder drugBuilder) {
this.name = drugBuilder.name;
this.type = drugBuilder.type;
this.taste = drugBuilder.taste;
this.size = drugBuilder.size;
this.num = drugBuilder.num;
}
@Override
public String toString() {
StringBuffer display = new StringBuffer();
display.append("---- 您获得了药品:" name " ----n");
display.append("能够治疗:" (type == 0 ? "内伤" : "外伤") "n");
display.append("口味:" (taste == 0 ? "甜味" : "咸味") "n");
display.append("颗粒尺寸:" size "cmn");
display.append("数量:" num "颗n");
return display.toString();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public int getTaste() {
return taste;
}
public void setTaste(int taste) {
this.taste = taste;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
药品建造类:
代码语言:javascript复制public class DrugBuilder {
/**
* 药品名称
*/
public String name;
/**
* 治疗类型(0:治疗内伤;1:治疗外伤)
*/
public int type;
/**
* 口味(1:甜味;2:咸味)
*/
public int taste;
/**
* 药品颗粒尺寸(单位cm)
*/
public int size;
/**
* 数量
*/
public int num;
/**
* 构建药品对象
* @return
*/
public Drug build() {
if(size < 1 || size > 5) {
throw new IllegalArgumentException("药品颗粒尺寸必须在1-5cm之间");
}
if(num < 1 || size > 10) {
throw new IllegalArgumentException("药品数量必须在1-10之间");
}
return new Drug(this);
}
/**
* 设置名称
* @param name
* @return
*/
public DrugBuilder setName(String name) {
this.name = name;
return this;
}
/**
* 设置治疗类型
* @param type
* @return
*/
public DrugBuilder setType(int type) {
this.type = type;
return this;
}
/**
* 设置口味
* @param taste
* @return
*/
public DrugBuilder setTaste(int taste) {
this.taste = taste;
return this;
}
/**
* 设置尺寸
* @param size
* @return
*/
public DrugBuilder setSize(int size) {
this.size = size;
return this;
}
/**
* 设置数量
* @param num
* @return
*/
public DrugBuilder setNum(int num) {
this.num = num;
return this;
}
}
客户类:
代码语言:javascript复制public class Demo {
public static void main(String[] args) {
Drug drug = new DrugBuilder()
.setName("大力丸")
.setType(1)
.setTaste(0)
.setSize(2)
.setNum(10)
.build();
System.out.println(drug);
}
}
输出:
代码语言:javascript复制---- 您获得了药品:大力丸 ----
能够治疗:外伤
口味:甜味
颗粒尺寸:2cm
数量:10颗
如果要创建的对象的属性很多,而且属性还有制约条件,比如尺寸必须要在1-5cm之间,数量必须在1-10之间,这样就比较适合用建造者模式,让对象的属性和创建分离。
这样对于开发者而言隐藏了复杂的对象构建细节,降低了学习成本,同时提升了代码的可复用性。
建造者模式在JDK源码中的应用
我们来看一下JDK的StringBuilder类,从名字就能看出来它使用了创建者模式,它提供append()方法,开放构造步骤,最后调用toString()方法就可以获得一个构造好的完整字符串,源码截图如下:
一个使用StringBuilder 的例子:
代码语言:javascript复制StringBuilder stringBuilder = new StringBuilder();
String str = stringBuilder
.append("a")
.append("b")
.append("c")
.toString();
总结
建造者模式主要适用于以下应用场景。
- 相同的方法,不同的执行顺序,产生不同的结果。
- 多个部件或零件,都可以装配到一个对象中,但是产生的结果又不相同。
- 产品类非常复杂,或者产品类中不同的调用顺序产生不同的作用。
- 初始化一个对象特别复杂,参数多,而且很多参数都具有默认值。
建造者模式与工厂模式的区别
建造者模式和工厂模式都是负责创建对象的,那么它们之间有什么区别呢?
工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象,工厂模式会马上返回创建的对象。
建造者模式重点关注如何分步生成复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象,允许你在获取对象前执行一些额外构造步骤。
可以看出来,建造者模式和工厂模式的目的是不同的
建造者模式的优点
- 封装性好,构建和表示分离。
- 符合单一职责原则,你可以将复杂构造代码从产品的业务逻辑中分离出来。
- 便于控制细节,建造者可以对创建过程逐步细化,而不对其他模块产生任何影响。
建造者模式的缺点
- 需要新增多个类, 因此代码整体复杂程度会有所增加。
- 如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。
后记
仙草药房推出了随身携带的个性化制药炉之后,在武林中获得众多好评,大家甚至自己开发出了苹果,橘子,水蜜桃口味的丹药,掀起了制作个性化丹药的热潮,也为武林人士闯荡江湖增添了不少乐趣。