一旦你熟悉了装饰的技巧,你将能够不修改任何底层代码的情况下,给你的(或别人的)对象赋予新的职责。
一、引入
星巴兹咖啡因为扩张速度太快了,他们准备更新订单系统,以合乎他们的饮料供应需求。
原先的设计类:
购买咖啡时,可以加入各种调味。星巴兹会根据加入的调料收取不同的费用。
违反设计原则:封装变化;多用组合,少用继承;
改法一:应用实例变量和继承。
1、在基类上加上调料实例,并在cost()方法中实现基本逻辑,让子类继承。
2、子类可以在其基础价格上加上超类价格(各种调料的价格)。
思考:一旦出现新的调料,我们要修改基类cost方法。
出现新的饮品,某些调料可能并不适合。
万一顾客想要双倍奶泡呢。
利用组合的方式,可以把设计超类时没有想到的职责加在对象上。而且,可以不用修改原来的代码。
设计原则:
类应该对扩展开放,对修改关闭。
允许类容易扩展,在不修改先有代码的情况下,就可搭配新的行为。(在需要的地方遵循 开放-关闭原则,过度的使用这个原则也是一种浪费,还会导致代码变得复杂且难以理解)。
二、装饰者模式
以饮料为主题,然后在运行时以调料来“装饰”(decorate)饮料。
比如说用户想要摩卡和奶泡深焙咖啡,那么,要做的是:
1、拿一个深焙咖啡(DarkRoast)的对象
2、以摩卡(Mocha)对象装饰它
3、以奶泡(whip)对象装饰它
4、调用cost()方法,并依赖委托(delegate)将调料的价钱加上去
小结:
1、装饰者和被装饰者对象有相同的超类型。
2、你可以用一个或多个装饰者包装一个对象。
3、既然装饰者和被装饰者对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。
4、装饰者可以在所委托被装饰者之前/或之后,加上自己的行为,以达到特定的目的。
5、对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。
装饰者模式:
动态的将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
星巴兹咖啡
抽象组件
1 // Beverage 是一个抽象类 2 public abstract class Beverage { 3 4 String description = "Unknown Beverage"; 5 6 // 对饮品的描述 7 public String getDescription() { 8 return description; 9 } 10 11 // cost要在子类中实现(都要实现) 12 public abstract double cost(); 13 }
抽象装饰者——调料Condiment抽象类
1 public abstract class CondimentDecorator extends Beverage { 2 3 public abstract String getDescription(); 4 5 }
具体组件——饮品
1 public class Espresso extends Beverage { 2 3 public Espresso() { 4 description = "Espresso"; 5 } 6 7 @Override 8 public double cost() { 9 return 2.99; 10 } 11 12 }
具体装饰者——调料
1 public class Milk extends CondimentDecorator { 2 3 Beverage beverage;// 用一个实例变量记录饮料,也就是被装饰者 4 5 public Milk(Beverage beverage) { 6 this.beverage = beverage;// 让被装饰者被记录到实例变量中。把饮料当做构造器参数,再由构造器将此饮料记录在实例变量中 7 } 8 9 @Override 10 public String getDescription() { 11 // 我们希望描述的不止是饮料,而是完整地连调料都描述出来。 12 return beverage.getDescription()+",Milk";// 我们利用委托的做法,得到一个叙述,然后再加上其他的附加描述。 13 } 14 15 @Override 16 public double cost() { 17 return beverage.cost() + 0.5;// 调用委托给被装饰对象,以计算价钱,然后再加上调料的价钱。 18 } 19 20 }
测试
1 public class StarbuzzCoffee { 2 3 public static void main(String[] args) { 4 Beverage beverage = new Espresso(); 5 System.out.println(beverage.getDescription()+" $"+beverage.cost());//Espresso $2.99 6 7 Beverage darkRoast = new DarkRoast(); 8 Beverage darkMocha = new Mocha(darkRoast); 9 Beverage dark2Mocha = new Mocha(darkMocha); 10 Beverage dark2MochaWhip = new Whip(dark2Mocha); 11 System.out.println(dark2MochaWhip.getDescription()+" $"+dark2MochaWhip.cost()); 12 // DarkRoast,Mocha,Mocha,Whip $4.7299999999999995 13 14 Beverage houseBlend = new HouseBlend(); 15 houseBlend = new Soy(houseBlend); 16 houseBlend = new Mocha(houseBlend); 17 houseBlend = new Whip(houseBlend); 18 System.out.println(houseBlend.getDescription() + " $"+houseBlend.cost()); 19 // HouseBlend,Soy,Mocha,Whip $3.2300000000000004 20 21 } 22 23 }
思考:
1、如果碰到打折呢
2、如果按大中小杯收费
3、想要展示Double Mocha 而不是Mocha,Mocha
优点:
为设计注入弹性
透明地插入装饰者,客户程序甚至不需要知道它是在和装饰者打交道
问题:
不易理解(要明确这些小类(调料)都是用来包装某个类(饮料)的)
插入装饰者过多,容易混淆(插入装饰者时要谨慎)
采用装饰者在实例化组件时,增加代码难度。
一旦使用装饰者模式,不只需要实例化组件,还要把次组件包装进装饰者中。
解决:
工厂(Factory)模式和生成器(Builder)模式,隐藏代码难度。
设计原则:
1、封装变化
2、多用组合,少用继承
3、针对接口编程,不针对实现编程
4、为交互对象之间的松耦合设计而努力
5、对扩展开放,对修改关闭
『装饰者模式』动态地将责任附加到对象上。想要扩展功能,装饰者提供有别于继承的另一种选择。
继承属于扩展形式之一,但不见得是达到弹性设计的最佳方式。
在我们的设计中,应该允许行为可以被扩展,而无须修改现有的代码。
组合和委托可用于在运动时动态地加上新的行为。
除了继承,装饰者模式也可以让我们扩展行为。
装饰者模式意味着一群装饰者类,这些类用来包装具体组件。
装饰者类反映出被装饰的组件类型(事实上,他们具有相同的类型,都经过接口或继承实现)。
装饰者可以在被装饰者的行为前面/或后面加上自己的行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的。
你可以用无数个装饰者包装一个组件。
装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型。
装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变的很复杂。