公告
如果您是第一次阅读我的设计模式系列文章,建议先阅读设计模式开篇,希望能得到您宝贵的建议。
定义
装饰者模式:即动态的给一个对象添加一些额外的职责。
场景举例
现在是2187年,智能机器人已经发展到可以一个新的高度。全球知名厂商 XX 正在推广新一代“机器人”,这次它的宣传口号是 “除了不能生孩子其它都可以做”,这满足了无数宅男们无尽的幻想。
某天顾客Alice 进入你的店铺,正在准备挑选她的下一任“机器人女友”。Alice首先购买了机器人的原型,选择价格99999元的价位的机器人原型。机器人原型皮肤表面很光滑(想象下母鸡褪了毛的样子~~)。于是你顺理成章的向Alice介绍了,本店新推出的“外形套餐”。
Alice希望他的机器人拥有 “大胸”、“肤白”、“瓜子脸”、“大眼睛”……(省略300个要求)。好吧,Alice并不算本店遇到的最挑剔的客户,但幸好我们拥有多于3000个可选方案,可供Alice挑选。
在一番深思熟虑之后,Alice最终选择了,“C罩杯”、“瓜子脸”、“丰满”、“翘臀”的组合套餐,该“套餐包”额外收费3000元。确定付款之后。
后台的3D打印机正在迅速的制造中,经历10分钟的漫长等待。Alice取到了他满意的机器人女友。
程序员视角
拆分角色
- 被装饰的对象(被装饰者) —— 机器人原型
- 可用于装饰的对象(装饰者) —— 肤色、胸型、身型、脸型等
- 客户端 —— 顾客Alice
重新描述该场景
客户端(Alice)
为 被装饰的对象(机器人原型)
添加了许多 装饰对象(肤色、胸型、脸型、身型)
。
如何实现
Bob
看到了你的门店生意火爆,同样是做机器人生意的他觉得有必要改良下他的传统售卖模式。
方案1 —— 最粗暴方式的实现
Bob
一番思考后心想,暂时想不到啥好办法。我先做出来,再慢慢思考有没有好办法。不然客人都要被你抢光了。
public class Customer {
public static void main(String[] args) {
Machine machine = new Machine();
machine.add(new Body("纤细"));
machine.add(new Chest("C罩杯"));
machine.add(new Butt("翘臀"));
machine.add(new Face("瓜子脸"));
}
}
public class Machine {
public void add(Body value) {
System.out.println(value);
}
public void add(Chest value) {
System.out.println(value);
}
public void add(Butt value) {
System.out.println(value);
}
public void add(Face value) {
System.out.println(value);
}
}
// 此处示意举例一个装饰对象,其他装饰对象与其结构一致
public class Body {
public String value;
public Body(String value) {
this.value = value;
}
@Override
public String toString() {
return "身型{" +
"value='" + value + '\'' +
'}';
}
}
运行结果:
添加身型{value='纤细'}
添加胸型{value='C罩杯'}
添加臀型{value='翘臀'}
添加脸型{value='瓜子脸'}
方案2 —— 隔离变化,抽象类型
Bob
很高兴的实现了方案1,但是总是有客户会提出新的奇葩的需求。每次客户提出新的需求的时候,Bob
都要修改“机器人”的生产程序,来匹配外观的修改。才能把机器人的外观,预览给客户看。这样下来一天Bob
只能服务几个客人,而对面的你的门店每天人进人出。
于是Bob
心想是否可以不改变机器人的源代码程序,修改其外观元素
。
方案2有一个思想需要注意:把
装饰物
装饰到机器人
身上,那么被装饰过的机器人依然是机器人。
所以无论是装饰物,还是被装饰物都需要实现同一个接口IDecorComponent
。
// 抽象装饰组件类型
public interface IDecorComponent {
}
public class Machine {
public void add(MachineContainer container) {
System.out.println("TODO:遍历容器,并Machine添加功能");
}
}
// 提取装饰容器,并去掉了对具体类型的耦合
public class MachineContainer implements IDecorComponent {
public void add(IDecorComponent component) {
System.out.println("添加了" + component);
}
}
// 装饰物实现了无方法的接口
public class Body implements IDecorComponent {
public String value;
public Body(String value) {
this.value = value;
}
@Override
public String toString() {
return "身型{" +
"value='" + value + '\'' +
'}';
}
}
运行结果:
添加了身型{value='纤细'}
添加了胸型{value='C罩杯'}
添加了臀型{value='翘臀'}
添加了脸型{value='瓜子脸'}
TODO:遍历容器,并Machine添加功能 // 纳尼?这里怎么还没做?
机器人组装完成
方案3 —— 装饰者模式
Bob
编译执行看了下结果,发现自己还遗漏了一个TODO
还未做。
他意识到一个重要的问题:
无论是委托给
装饰容器
还是机器人
本身都避免不了要为机器人
添加装饰物
这事。
既然在劫难逃,那就只能勇敢面对了。
最终的方案:
public class Customer {
public static void main(String[] args) {
Machine machine = new Machine();
// 配置装饰容器
IDecorComponent component = new MachineContainer(machine);
component = new Body(component, "纤细");
component = new Butt(component, "翘臀");
component = new Chest(component, "C罩杯");
component = new Face(component, "瓜子脸");
// 触发添加行为
component.addBehiavor();
}
}
public interface IDecorComponent {
void addBehiavor();
}
public class Machine {
// 对Machine 没有任何侵入
}
public class MachineContainer implements IDecorComponent {
private Machine machine;
public MachineContainer(Machine machine) {
this.machine = machine;
}
public void add(IDecorComponent component) {
System.out.println("添加了" + component);
}
@Override
public void addBehiavor() {
System.out.println("===触发了装饰物的行为===");
}
}
// 新增的类,用于委托递归操作
public class Decor implements IDecorComponent {
IDecorComponent component;
public Decor(IDecorComponent component) {
this.component = component;
}
@Override
public void addBehiavor() {
// 委托装饰的组件继续执行相关的行为
this.component.addBehiavor();
}
}
public class Body extends Decor {
public String value;
public Body(IDecorComponent component, String value) {
super(component);
this.value = value;
}
@Override
public String toString() {
return "身型{" +
"value='" + value + '\'' +
'}';
}
@Override
public void addBehiavor() {
// 触发了递归操作
super.addBehiavor();
System.out.println("添加了" + toString());
}
}
运行结果如下:
===触发了装饰物的行为===
添加了身型{value='纤细'}
添加了臀型{value='翘臀'}
添加了胸型{value='C罩杯'}
添加了脸型{value='瓜子脸'}
总结
装饰者模式装饰模式的本质:动态组合。
应用装饰模式的注意点:
各个装饰器之间最好是完全独立的功能,不要有依赖,这样在进行组合的时候才没有先后顺序的限制。否则会大大降低装饰组合的灵活度。
装饰模式不仅可以增加功能,可以增加功能的访问(这点可以参考:职责链模式)。也可以限制功能的执行的先后顺序(递归的思想)。
总之装饰模式是通过把复杂的功能简单化、分散化(注意:会产生比较多的子类)。然后再根据需求动态的组合这些功能(子类)。
建议用法:在不影响其他对象的情况下,透明的添加职责时。
网友评论