Decorator
装饰者模式:(装饰者)动态的将责任附加到对象(被装饰者)上去,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
模式中的角色
- 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。
- 具体构件(ConcreteComponent)角色:定义一个将要接收附加责任的类。
- 装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
具体装饰(ConcreteDecorator)角色:负责给构件对象“贴上”附加的责任。
装饰模式类图如下
特点
- 装饰者和被装饰者有相同的超类型
- 对象可以在任何时候被装饰,所以可以在运行时动态地,不限量地用你喜欢的装饰者来装饰。
示例
星巴克是以扩张速度最快而闻名的咖啡连锁店。如果你在结交看到它的店,在对面街上肯定也会另一家。因为扩展速度实在太快了,他们准备更新订单系统,以合乎他们的饮料供应要求。
他们原先的类设计是这样……
1. Beverage(饮料)是所有具体饮料(HouseBlend:黑咖啡,DarkRoast:深度烘焙咖啡,Decaf:脱咖啡因咖啡,Espresso:意式浓缩咖啡)的抽象,定义一个具体方法getDescription,和抽象的cost方法,计算咖啡价钱,此方法需要具体类实现。
2. HouseBlend、Decaf、DarkRoast和Espresso继承自Beverage。
现在问题来了,在购买咖啡的时候可以加入各种作料,例如蒸奶、豆浆、摩卡或者奶泡。星巴克会根据多加入的作料收取不同的费用,所以订单系统必须考虑到这些作料的价钱。下面是他们进行的第一次修改尝试。
每个类都实现不同的cost方法。很明显,星巴克为自己创造了一个维护噩梦,若果牛奶的价钱上扬,怎么办?是不是需要修改多有涉及到牛奶类的源代码。新增一种焦糖调料风味时,怎么办?是不是需要设计一些添加焦糖的咖啡类。
下面是他们进行的第二次修改尝试。
笨透了,干嘛设计这么多类呀?利用实例变量和继承,就可以追踪这些调料呀?
重新设计了Beverage类,加上实例变量代表是否加上了作料,提供了cost方法的实现,计算加入各种作料咖啡的价钱,cost的实现如下
Beverage子类同过 set作料 加入不同的作料,通过调用父类的cost方法完成价钱的计算。
这下子类的数目少了很多,可是还有很多问题没有解决:
- 调料的价钱改变了该怎么办
- 出现新的作料该怎么办
- 万一顾客想加入两份摩卡该怎么办
下面是他们进行的第三次修改尝试:以装饰者构造饮料订单
代码示例
Beverage类
public abstract class Beverage{
String description = "Unknown Beverage";
public String getDescrption(){
return descrition;
}
public abstract double cost();
}
具体的饮料代码
public class Expresso extends Beverage{
public Espresso(){
description = "Espresso";
}
public double cost(){
return 1.99;
}
}
public class HouseBlend extends Beverage{
public HouseBlend (){
description = "HouseBlend ";
}
public double cost(){
return .89;
}
}
Condiment作料类抽象类,也即是装饰者类
public abstract class CondimentDecorator extends Beverage{
public abstract String getDescrption();
}
具体的调料类代码
public class Mocha extends CondimentDecorator{
Beverage beverage;
public Mocha(Beverage beverage){
this.beverage = beverage;
}
public String getDescrption(){
return beverage.getDescrption() + ",Mocha";
}
public double cost(){
return .20 + beverage.cost();
}
}
测试代码
public class StarbuzzCoffee{
//加双份mocha的咖啡
Beverage beverage = new Espresso();
beverage = new Mocha (beverage);
beverage = new Mocha (beverage);
}
半透明的装饰模式
装饰模式有透明和半透明两种,这两种的区别就在于装饰角色的接口与抽象构件角色的接口是否完全一致。透明的装饰模式也就是理想的装饰模式,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致。相反,如果装饰角色的接口与抽象构件角色接口不一致,也就是说装饰角色的接口比抽象构件角色的接口宽的话,这种装饰模式也是可以接受的,称为“半透明”的装饰模式。
半透明的装饰模式举例
InputStream类型中的装饰模式是半透明的。为了说明这一点,不妨看一看作装饰模式的抽象构件角色的InputStream的源代码。这个抽象类声明了九个方法,并给出了其中八个的实现,另外一个是抽象方法,需要由子类实现。
public abstract class InputStream implements Closeable {
public abstract int read() throws IOException;
public int read(byte b[]) throws IOException {}
public int read(byte b[], int off, int len) throws IOException {}
public long skip(long n) throws IOException {}
public int available() throws IOException {}
public void close() throws IOException {}
public synchronized void mark(int readlimit) {}
public synchronized void reset() throws IOException {}
public boolean markSupported() {}
}
下面是作为装饰模式的抽象装饰角色FilterInputStream类的源代码。可以看出,FilterInputStream的接口与InputStream的接口是完全一致的。也就是说,直到这一步,还是与装饰模式相符合的。
public class FilterInputStream extends InputStream {
protected FilterInputStream(InputStream in) {}
public int read() throws IOException {}
public int read(byte b[]) throws IOException {}
public int read(byte b[], int off, int len) throws IOException {}
public long skip(long n) throws IOException {}
public int available() throws IOException {}
public void close() throws IOException {}
public synchronized void mark(int readlimit) {}
public synchronized void reset() throws IOException {}
public boolean markSupported() {}
}
下面是具体装饰角色PushbackInputStream的源代码
public class PushbackInputStream extends FilterInputStream {
private void ensureOpen() throws IOException {}
public PushbackInputStream(InputStream in, int size) {}
public PushbackInputStream(InputStream in) {}
public int read() throws IOException {}
public int read(byte[] b, int off, int len) throws IOException {}
public void unread(int b) throws IOException {}
public void unread(byte[] b, int off, int len) throws IOException {}
public void unread(byte[] b) throws IOException {}
public int available() throws IOException {}
public long skip(long n) throws IOException {}
public boolean markSupported() {}
public synchronized void mark(int readlimit) {}
public synchronized void reset() throws IOException {}
public synchronized void close() throws IOException {}
}
查看源码,你会发现,这个装饰类提供了额外的方法unread(),这就意味着PushbackInputStream是一个半透明的装饰类。换言之,它破坏了理想的装饰模式的要求。如果客户端持有一个类型为InputStream对象的引用in的话,那么如果in的真实类型是 PushbackInputStream的话,只要客户端不需要使用unread()方法,那么客户端一般没有问题。但是如果客户端必须使用这个方法,就必须进行向下类型转换。将in的类型转换成为PushbackInputStream之后才可能调用这个方法。但是,这个类型转换意味着客户端必须知道它 拿到的引用是指向一个类型为PushbackInputStream的对象。这就破坏了使用装饰模式的原始用意。
现实世界与理论总归是有一段差距的。纯粹的装饰模式在真实的系统中很难找到。一般所遇到的,都是这种半透明的装饰模式