定义
装饰者模式采用组合的方式,动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。组合和委托可用于在运行时动态地添加新的行为。
装饰者模式实现
设计冰淇淋小站
炎炎夏日,我们都喜欢吃冰淇淋解暑,有各种口味,完了我还可以选择喜欢的调味酱加在冰淇淋上,我要设计一个冰淇淋的售卖系统,该怎么设计呢?这里就需要用到装饰者模式,装饰者是调味酱,被装饰者是冰淇淋
用调味酱将冰淇淋包装一层,就是说将冰淇淋对象传入调味酱对象中进行扩展,这个我可以理解,但是我不明白,为啥冰淇淋包装类IceCreamDecorator要与冰淇淋有相同的超类呢,试想一下,我买冰淇淋,加了调味酱之后,递给我的变成了一杯奶茶,这不是我想要的呀,因此装饰者和被装饰者必须是一样的类型,即有共同的超类,这样装饰者才能取代被装饰者,我们利用继承达到类型匹配,而不是利用继承获得行为。包装前是IceCream对象,包装完了还是IceCream对象,装饰者对被装饰者的客户是透明的,除非客户程序依赖于被装饰者的具体类型。
冰淇淋小站代码实现
/**
* 超类
* Created by zhoupengxiao on 16/6/16.
*/
public abstract class IceCream {
//获取名称;
public abstract String getDescription();
//获取价格;
public abstract double cost();
}
/**
* 原味冰淇淋
* Created by zhoupengxiao on 16/6/16.
*/
public class OriginalTaste extends IceCream {
@Override
public String getDescription() {
return "原味";
}
@Override
public double cost() {
return 3.5;
}
}
/**
* 抹茶冰淇淋
* Created by zhoupengxiao on 16/6/16.
*/
public class MatchaTaste extends IceCream {
@Override
public String getDescription() {
return "抹茶味";
}
@Override
public double cost() {
return 4.0;
}
}
/**
* 装饰类
* Created by sarahzhou on 16/6/16.
*/
public abstract class IceCreamDecorator extends IceCream {
}
/**
* 蓝莓酱
* Created by sarahzhou on 16/6/16.
*/
public class BlueberrySauce extends IceCreamDecorator {
//被包装者
private IceCream iceCream;
public BlueberrySauce(IceCream iceCream) {
this.iceCream = iceCream;
}
@Override
public String getDescription() {
return iceCream.getDescription() + " 蓝莓酱";
}
@Override
public double cost() {
return iceCream.cost() + 0.8;
}
}
/**
* 芒果酱
* Created by zhoupengxiao on 16/6/16.
*/
public class MangoSauce extends IceCreamDecorator {
//被包装者
private IceCream iceCream;
public MangoSauce(IceCream iceCream) {
this.iceCream = iceCream;
}
@Override
public String getDescription() {
return iceCream.getDescription() + " 芒果酱";
}
@Override
public double cost() {
return iceCream.cost() + 0.6;
}
}
/**
* 巧克力酱
* Created by sarahzhou on 16/6/16.
*/
public class ChocolateSauce extends IceCreamDecorator {
//被包装者
private IceCream iceCream;
public ChocolateSauce(IceCream iceCream) {
this.iceCream = iceCream;
}
//获取名称;
@Override
public String getDescription() {
return iceCream.getDescription() + " 巧克力酱";
}
//获取价格; 0.5是巧克力酱的价格;
@Override
public double cost() {
return iceCream.cost() + 0.5;
}
}
测试类和输出结果
public class TestDecoration {
@Test
public void test1() {
IceCream iceCream = new OriginalTaste();
iceCream = new MangoSauce(iceCream);
iceCream = new BlueberrySauce(iceCream);
iceCream = new BlueberrySauce(iceCream);
System.out.println(iceCream.getDescription() + " : ¥" + String.format("%.2f", iceCream.cost()));
IceCream iceCream1 = new MatchaTaste();
iceCream1 = new ChocolateSauce(iceCream1);
iceCream1 = new ChocolateSauce(iceCream1);
iceCream1 = new BlueberrySauce(iceCream1);
iceCream1 = new MangoSauce(iceCream1);
System.out.println(iceCream1.getDescription() + " : ¥" + String.format("%.2f", iceCream1.cost()));
}
}
如何理解组合和委托?
组合Composition顾名思义,使用成员变量引用的方式,一个组件和不同的装饰者自由结合,这就是组合;委托Delegation呢,看这段代码
//获取价格; 0.5是巧克力酱的价格;
@Override
public double cost() {
return iceCream.cost() + 0.5;
}
用巧克力酱包装冰淇淋,计算价格时,我先调用被装饰者的cost()方法,在返回结果基础上加上巧克力酱的价格;即,执行装饰者的方法时,先委托被装饰者执行,在此基础上加上装饰者的逻辑。是不是有点眼熟?没错,这跟子类先调用父类方法,再扩展子类逻辑很相似,因此呢,我们可以利用组合委托的方式实现继承的功能,区别在于,前者在运行时决定,后者在编译时就决定了。
装饰者模式与继承的比较
我们都知道一个设计原则多用组合,少用继承,可是为什么这样子呢?比如上面我们用的冰淇淋的例子,有2个基本组件:原味冰淇淋和抹茶冰淇淋,调味酱有3种:蓝莓酱、芒果酱、巧克力酱。OK,有同学说,我们完全没必要用什么装饰者模式,用继承就可以了,那我们试一下,为了满足不同人的口味,我们需要定义各种不同的IceCream子类:OriginalWithBlueberry, OriginalWithMango, OriginalWithChocolate, MatchaWithBlueberry, MatchaWithMango, MatchaWithChocolate,用简单的排列组合能得出6种可能,才6个类,不多嘛,可是,完了吗?如果我要原味冰淇淋加2份蓝莓酱呢,OriginalWith2Blueberrys?再加1份巧克力酱呢,OriginalWith2BlueberrysAndChocolate?我的天,这样下去,我算算我得写多少个子类,我发现我无从得知,因为顾客的要求是无法一一列举的,比如一些重口味的食客买了一只冰淇淋,却要我加5份蓝莓酱、2份巧克力酱、3份芒果酱,虽然很奇葩,但我也不能拒绝呀。天呐,这样子我得扩展多少个IceCream子类哦?!!明显这里用装饰者模式要合适得多。
组合关系
- 运行时扩展,在运行时可以选择不同的装饰者来进行组合,参考冰淇淋例子
- 是has a的关系
- 不破坏封装,整体类和局部类松耦合,相对独立
- 整体类对局部类进行包装,在被装饰者的行为之前/之后加上自己的行为,以达到特定的目的
- 利用组合composition和委托delegation可以在运行时具有继承的效果,并且更有弹性,更好维护
继承关系
- 编译时静态扩展,运行时无法动态扩展,因为子类无法改变已继承的父类
- 是is a的关系
- 破坏封装,子类和父类紧密耦合,子类依赖父类,缺乏独立性
- 子类可能从父类继承了并不需要的功能,造成子类冗余
- 要实现多重继承的设计时,java的extends无法实现,因为java只支持单继承,这时需要使用组合使得一个类包含多个类的功能
装饰者模式在java中的应用
装饰者模式在java中的典型应用就是输入输出流了。
这里FilterInputStream就是输入流包装类(装饰者),BufferedInputStream为输入流组件增强了缓存输入的功能,LineNumberInputStream为输入流组件增强了追踪行号的功能。