回顾之前学到的工厂模式,装饰模式也属于一种用于创建特定对象的模式。区别在于,工厂模式,通过一个Factory对象来创建我们所需的对象,并且,讲创建过程封装进对应的Factory中,在外层无需知道对象到底是如何创建出来的;而对于装饰模式,我们完全可以在最外层控制对象的创建过程;从某种层面说,装饰模式就像是制作一杯清咖啡之后,通过一层一层的包装往里面添加其他佐料,而工厂模式则讲这一过程封装进了实际工厂类中。
这里先提出两个比较有用的词:Component和Decorator。
Component:如同下图的Espresso,是需要被装饰的对象,派生出多个Concrete component。
Decorator:如同下图的Mocha,Whip,装饰者类,用于装饰一个Component。
先来看一下装饰模式的抽象图表示:
这是一杯Double Mocha+Whip的Espresso(双倍摩卡+搅拌奶油+浓缩咖啡)。我的最爱啊!
好了,那要如何在java代码中实现这样一杯咖啡呢?
一般来说,我们一定会创建Coffee作为基类,然后将某个特定的类,比如这里的Double Mocha,去继承它。可是问题在于,假如客户不要double,是single呢?客户不需要浓缩咖啡,要美式咖啡呢?抑或是其他种类的咖啡呢?如果每一种都去继承基类,那日后的维护工作可就蛋疼了。
来看看装饰模式如何应对:
抽象基类,Beverage:
public abstract class Beverage {
String description="no description";
public String getDescription(){
return this.description;
}
public abstract double cost();
}
浓缩咖啡类,Espresso:
public class Espresso extends Beverage {
public Espresso(){
this.description="Espresso";
}
@Override
public double cost() {
// TODO Auto-generated method stub
return 1.99;
}
}
附加品基类,Condiment:
public abstract class CodimentDecorator extends Beverage {
Beverage beverage;
public abstract String getDescription();
}
Mocha:
public class Mocha extends CodimentDecorator {
public Mocha(Beverage bev){
this.beverage=bev;
}
@Override
public String getDescription() {
// TODO Auto-generated method stub
return beverage.getDescription()+", Mocha";
}
@Override
public double cost() {
// TODO Auto-generated method stub
return beverage.cost()+.20;
}
}
Whip:
public class Whip extends CodimentDecorator {
// Beverage beverage;
public Whip(Beverage bev){
this.beverage=bev;
}
@Override
public String getDescription() {
// TODO Auto-generated method stub
return beverage.getDescription()+", Whip";
}
@Override
public double cost() {
// TODO Auto-generated method stub
return beverage.cost()+.10;
}
}
Main函数:
public static void main(String[] args) {
// TODO Auto-generated method stub
Beverage espresso =new Espresso();
espresso=new Mocha(espresso);
espresso=new Mocha(espresso);
espresso=new Whip(espresso);
System.out.println(espresso.getDescription()+"costs: "+espresso.cost());
}
运行结果:
Espresso, Mocha, Mocha, Whipcosts: 2.49
这里关键在于,Beverage类是之后所有类的父类,是最上层的,通过多层继承,并且在Condiment类中hold住一个beverage的引用,来实现装饰模式,如此一来,便可将beverage和condiment分离,而今后即便是要添加一种新型饮料且不是咖啡品种,只要添加对应的beverage子类和对应的condiment类,即可实现,而不用修改现有的类。
在java中,其实已经存在了一种利用装饰模式实现的标准库,就是java io。一开始当我使用java io的时候,发现各种各样的input 和 output类,看得头昏眼花,不知道何时该用什么。其实,所有的输入输出类都继承自InputStream和OutputStream,并且在这基础上,对数据流做了不同的处理,才会出现DataInputStream,BufferedInputStream等等。如下图:
(PS:图是网上截的,貌似输出流和输入流这位作者都没写清楚。。。总之这里都是输入流,写错的地方请无视)
其中FilterInputStream就是一个装饰者的基类。从java doc中可以看到,它只是重写了InputStream的函数,但是并没有订制特定的函数,而这些特例都将在它的子类,也就是上面这幅图的最右边中实现。FilterInputStream只是一个提供给后面子类实现的Container。(PS:可以查看FilterInputStream的源代码)
所以我们经常会用到这样的代码:
InputStream ins=new ByteArrayInputStream(new BufferedInputStream(System.in));
对于很多初学者来说,看到这样的代码肯定头都晕了,我一开始也是,不过如果知道了装饰模式,这就很好理解了。其实ByteArrayInputStream就是一个Concrete Component,而之后的BufferedInputStream是一个Concrete Decorator。意思就是把控制台输入数据存入缓存,然后再变成字节数组输入流对象以便后续之用。
那么装饰者模式有什么坏处呢?其实很容易发现,component和decorator一多,势必是class explosion。后果。就不多说了。。。