设想一个情景,假设有一家奶茶店,店里面只卖饮料,不卖其他的东西,且饮料的品种只有两种分别为Coffee和Milk,那么为这家店的订单系统可以简单的设计为两个类,Coffee类和Milk类。如果有一天,店里面卖的饮料品种增加了很多种,这个时候我们可以发现饮料种类很多,但是每一类都可以抽象出类似的属性和方法,每个种类都有一个描述该类饮料的属性description以及一个获取价格的方法cost()。于是我们抽象出来一个抽象类(或者接口)Drink,并且让每个种类的饮料都继承Drink(或实现Drink接口)。
public abstract class Drink {
protected String description;
public String getDescription() {
return description;
}
public abstract float cost();
}
public class Coffee extends Drink {
public Coffee() {
this.description = "coffee";
}
@Override
public float cost() {
return 12;
}
}
public class Milk extends Drink {
public Milk() {
this.description = "milk";
}
@Override
public float cost() {
return 10;
}
}
这样之后,以后每增加一个品种,只需要继承Drink类就可以了。
但是,有一天,店主为了增加销量,提出了一个新的主意,那就是可以在饮料里面添加各种各样的配料,诸如Chocolate和Sugar等,每一种调料都有各自的价格。这样一来,不同品种可以加上不同的调料,最终的价格也就各不相同。那么此时如何改进这个订单系统比较好呢。
一种方式是为每一种主料和各种配料的组合实现一个新的饮料品种类,比如coffee+sugar类,milk+chocolate类,coffee+sugar+chocolate类。但是这样一来,当主料和配料的种类较多之后,类的数量将呈现一个爆炸式的增长。更不好的是,假设当某一种主料或者配料的价格发生了变化时,需要维护的类的数量是巨大的。所以,这种设计是非常糟糕的。
这个时候,装饰者模式就可以派上用场了。什么是装饰者模式呢,就以这个例子来说,我们的Milk类和Coffee类是饮料的一种,所以我们让他们从Drink类继承。另外我们还有各种各样可以添加的配料,虽然配料不是饮料,但是我们可以想象,某个品种的饮料在添加了某一种或多种的配料之后,它仍然是属于一种饮料。所以我们将配料(Sugar、Chocolate等)也继承自Drink类。(或许,将Sugar类的名称改为ADrinkAddSugar更容易理解)。表示添加了此种配料的一种饮料。
这样一来,不管是主料还是配料全都继承(或实现)了Drink,我们就可以在每个配料的类里面增加一个Drink类型的变量,这个变量指的就是需要添加此种配料的饮料的一个引用(有可能这个饮料之前已经添加过了其他的配料,但是无所谓,所有的主料配料都继承了Drink,所以它仍然是一种Drink)。这样一来,我们在配料的description和cost()方法里面,就可以根据这个引用获取添加此配料之前的相关信息(如品种描述和价格等),然后在这个配料类里面加上有关这个配料的信息。
这样一来,我们只需要维护很少的类就可以实现相同的功能。下面是例子代码:
public class Sugar extends Drink {
private Drink drink;
public Sugar(Drink d) {
this.drink = d;
}
public String getDescription() {
return drink.getDescription() + "+sugar";
}
@Override
public float cost() {
return drink.cost() + 2;
}
}
public class Chocolate extends Drink {
private Drink drink;
public Chocolate(Drink d) {
this.drink = d;
}
public String getDescription() {
return drink.getDescription() + "+chocolate";
}
@Override
public float cost() {
return drink.cost() + 3;
}
}
下面是测试代码,首先我们创建了一个牛奶的饮料,打印出相关的信息。然后我们根绝这个牛奶创建了一杯Milk+Sugar的饮料,并打印出了它的相关信息,最后我们又根据这杯加过了Sugar的饮料创建了一杯加了Chocolate的饮料。
public class Test {
public static void main(String[] args) {
Drink d = new Milk();
System.out.println(d.getDescription() + ":" + d.cost());
d = new Sugar(d);
System.out.println(d.getDescription() + ":" + d.cost());
d = new Chocolate(d);
System.out.println(d.getDescription() + ":" + d.cost());
}
}
程序运行结果如下:
milk:10.0
milk+sugar:12.0
milk+sugar+chocolate:15.0
那么为什么这种模式叫装饰者模式呢?我们可以从测试程序看出,我们首先是创建了一杯只含有主料的饮料,接下来我们是对这杯饮料添加了各种各样的配料,就好像是我们有一间空房子,我们买了各种各样的东西来装饰它一样。装饰的顺序可以有所不同,但是装饰之后它仍然是一间房子,我们仍然可以继续对它进行装饰。
在JDK中,IO流部分就是用了大量的装饰者模式!