装饰者模式(head first 设计模式)
定义
装饰着模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
体现的OO原则: 对扩展开放,对修改关闭。
特点
- 装饰者和被装饰对象具有相同的超类型
- 可以用一个或多个装饰者包装一个对象
- 由于装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装)的场合,都可以用装饰过的对象代替她
- 装饰者可以在所委托被装饰者的行为之前或之后,加上自己的行为,以达到特定的目的
- 对象可以在任何时候被装饰,所以可以在运行时动态的、不限量的用需要的装饰者来装饰对象
类图
思考:为什么Decorate类扩展自Component类?
- 装饰者和被装饰者必须是一样的类型,我们在此使用继承达到“类型匹配”
- 类型匹配意味着装饰者和被装饰者具有相同的接口,从而装饰者可以取代被装饰者
- 新的行为并不是继承自超类,而是由组合对象得到,即所有饮料和调料可以更有弹性的加以混合和匹配
- 我们可以在任何时候,实现新的装饰者增加新的行为。如果依赖继承,每当需要新行为时,必须修改代码
- Component类型可以使用抽象类,也可以使用接口
星巴兹咖啡销售系统装饰者模式类图关系:
例子
Beverage 组件类
/** * Beverage(饮料类)相当于抽象的 Component 类 */ public abstract class Beverage { String desc = "Unknown Beverage"; public String getDesc(){ return desc; } public abstract double cost(); }
四个具体组件
/**
* Espresso 浓缩咖啡
* 1.让 Espresso扩展自 Beverage 类
* 2.设置饮料的描述
* 3.实现 cost 方法,计算 Espresso 的价钱,先不管调料的价钱
*/
public class Espresso extends Beverage {
public Espresso(){
desc = "Espresso";
}
@Override
public double cost() {
return 1.99;
}
}
HouseBlend、DarkRoast、Decat 类与 Espresso 类 类似。
- 装饰者共同实现的接口(抽象类)
/**
* Condiment(调料)抽象类,装饰者类
* 1.首先,必须让 CondimentDecorator 能够取代 Beverage 类,所以扩展自 Beverage 类
* 2.所有的调料装饰者都必须重新实现 getDesc()方法
*/
public abstract class CondimentDecorator extends Beverage{
public abstract String getDesc();
}
- 装饰者
/**
* 1.摩卡是一个装饰者,所以让它继承 CondimentDecorator,而 CondimentDecorator 继承 Beverage
* 2.要染 Mocha 能够引用一个 Beverage,做法:a.用一个实例变量记录饮料,也就是被装饰着
* b.构造函数传入beverage
* 3.更改描述和价格
*/
public class Mocha extends CondimentDecorator{
Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDesc() {
return beverage.getDesc() + ", Mocha";
}
@Override
public double cost() {
return 0.20 + beverage.cost();
}
}
Soy、Whip类也与 Mocha 类似。
5.测试
private void testDecorator() {
Beverage beverage = new Espresso();
Log.d(TAG, beverage.getDesc() + "$" + beverage.cost());
Beverage beverage1 = new DarkRoast();
beverage1 = new Mocha(beverage1);
beverage1 = new Mocha(beverage1);
beverage1 = new Whip(beverage1);
Log.d(TAG, beverage1.getDesc() + "$" + beverage1.cost());
Beverage beverage2 = new HouseBlend();
beverage2 = new Soy(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
Log.d(TAG, beverage2.getDesc() + "$" + beverage2.cost());
}
结果:
D/MainActivity: Espresso$1.99
D/MainActivity: DarkRoast, Mocha, Mocha, Whip$1.44
D/MainActivity: House Blend Coffee, Soy, Mocha, Whip$1.3399999999999999
java I/0 中的装饰者
LineNumberInputStream( BufferedInputStream( FileInputStream ) )
- LineNumberInputStream 是一个具体的装饰者,它加上了计算行数的能力。
- BufferedInputStream 是一个具体的装饰者,它加入两种行为:缓冲输入、readline()方法
- FileInputStream 是被装饰的组件。 I/O库提供了几个组件,包括:FileInputStream、StringBufferInputStream、ByteArrayInputStraam…
自定义自己的 I/O 装饰者
读取文件,大写转小写:
/**
* 扩展 FilterInputStream 类,这是所有 InputStream 的抽象装饰者
*/
public class LowerCaseInputStream extends FilterInputStream {
public LowerCaseInputStream(InputStream in) {
super(in);
}
/**
* 针对字节
* @return
* @throws IOException
*/
public int read() throws IOException{
int c = super.read();
return (c == -1 ? c : Character.toLowerCase((char)c));
}
/**
* 针对字节数组
* @param b
* @param offset
* @param len
* @return
* @throws IOException
*/
public int read(byte[] b, int offset, int len) throws IOException{
int result = super.read(b, offset, len);
for (int i = offset; i < offset + result; i++){
b[i] = (byte) Character.toLowerCase((char)b[i]);
}
return result;
}
}
使用:
int c;
try {
//得到资源中的Raw数据流
InputStream in = getResources().openRawResource(R.raw.test);
in = new LowerCaseInputStream(in);
while ((c = in.read()) >= 0){
Log.d(TAG, (char)c + "");
//System.out.print((char)c);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
总结
在我们的代码中,应该允许行为遵循对扩展开放-对修改关闭的原则,这样就可以无需修改现有的代码就可以实现我们扩展的功能。
装饰者模式意味着一群装饰者类,这些类用来包装具体组件。
装饰者反映出被装饰者的组件类型(具有相同的类型)
装饰者可以在被装饰者的行为前面或后面加上自己的行为,甚至将被装饰者的行为整个取代,而达到特定的目的。
可以用无数个装饰者包装一个组件。
装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型。
装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得复杂。