定义
动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
装饰者对象和被装饰的对象都实现了相同的操作接口,装饰者将被装饰者包装起来,在同名的接口方法中,在调用被装饰者的方法之前或者之后做一些自己的操作,这样在外部调用者来看,就相当于被“装饰”了一样。
咖啡的例子
首先咖啡是一种饮料,然后咖啡还可以加调料如加糖加牛奶等等。饮料是抽象的构件基类,咖啡作为具体的构件实现类,调料就是我们抽象的装饰者基类,然后会有具体的饮料装饰者实现类。在基类中我们给饮料添加两个行为,一个是获取饮料的描述,一个是获取饮料的价格。类图设计如下:
其中Beverage是抽象的饮料类,Coffee是具体的咖啡饮料类,BeverageDecorator是抽象的饮料装饰者类,SugarCoffee、 LemonCoffee和MilkCoffee是具体的饮料装饰类,分别表示给咖啡加糖、加柠檬和加牛奶。
相关实现代码:
/**
* 饮料抽象类
*/
public abstract class Beverage {
protected String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double getPrice();
}
/**
* 咖啡饮料
*/
public class Coffee extends Beverage {
public Coffee() {
description = "咖啡饮料";
}
@Override
public double getPrice() {
return 10.00;
}
}
饮料装饰者抽象类
/**
* 饮料装饰者抽象类
*/
public abstract class BeverageDecorator extends Beverage {
private Beverage beverage;
public BeverageDecorator(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription();
}
@Override
public double getPrice() {
return beverage.getPrice();
}
}
//这个类是可以不要的,直接继承基类
装饰者类
/**
* 装饰者类,负责给咖啡加糖
*/
public class SugarCoffee extends BeverageDecorator {
public SugarCoffee(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return super.getDescription() + ",加糖";
}
@Override
public double getPrice() {
return super.getPrice() + 2.00;
}
}
/**
* 装饰者类,负责给咖啡加牛奶
*/
public class MilkCoffee extends BeverageDecorator {
public MilkCoffee(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return super.getDescription() + ",加牛奶";
}
@Override
public double getPrice() {
return super.getPrice() + 3.00;
}
}
/**
* 装饰者类,负责给咖啡加柠檬
*/
public class LemonCoffee extends BeverageDecorator {
public LemonCoffee(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return super.getDescription() + ",加柠檬";
}
@Override
public double getPrice() {
return super.getPrice() + 4.00;
}
}
测试
public class Client {
public static void main(String[] args) {
//创建一种叫咖啡的饮料
Beverage coffee = new Coffee();
//给咖啡加糖
coffee = new SugarCoffee(coffee);
//给咖啡加牛奶
coffee = new MilkCoffee(coffee);
//给咖啡加柠檬
coffee = new LemonCoffee(coffee);
System.out.println("你点的饮料是:"+coffee.getDescription()+"\n"+"价格是:"+coffee.getPrice()+"元");
}
}
可以看到,我们要生成各种调料的咖啡,代码就很简单,只要调用构造函数,一层一层的往上套就可以了,每加一层饮料修饰,咖啡就具有了新的饮料特性以及售价。并且这里顺序其实也是可以调整的,不过对这个例子而言顺序调整没有太大意义,但在实际当中或许是有用的。
仔细思考,经过多次装饰的过程中我们的咖啡究竟发生了哪些变化?
以获取售价为例,当经过SugarCoffee之后getPrice()会先获取原始的咖啡售价,然后再加上Sugar自己的售价,最后返回。在经过MilkCoffee装饰之后,getPrice()则会先获取经过SugarCoffee装饰之后的价格,然后加上Milk自己的价格,最后返回。在经过LemonCoffee装饰之后,getPrice()则会先获取经过MilkCoffee装饰之后的价格,然后加上Lemon自己的价格,最后返回。整个过程就像剥洋葱一样,层层嵌套,当然貌似有点递归的意思在里面。
通过下面的图可以帮助你很好的理解装饰者与原始对象之间的关系:
Java中的装饰者
Java中的java.io包里面的InputStream类和OutputStream类就是装饰者模式。
刚接触java的时候,相信你一定见过类似下面的代码:
InputStream inputStream = null;
OutputStream outputStream = null;
try {
outputStream = new BufferedOutputStream(new FileOutputStream(new File("test2.txt")));
inputStream = new DataInputStream(new BufferedInputStream(new FileInputStream(new File("text.txt"))););
byte[] buffer = new byte[1024];
int length = 0;
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
} catch (Exception e) {
e.printStackTrace();
}
实际上在涉及输入流输出流的操作时,都会进行一些嵌套使用,如果对装饰者模式不熟悉,你可能会困惑或者不理解为什么java的输入流要嵌套这么多层。其实每一层都有自己实现的一些特性:
这些都是装饰者模式的实现,实际上在java的io包中存在着数量众多的装饰者类,单看InputStream就有好多:
优缺点
装饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任。
同时也有缺点,那就是装饰者实现类可能会很多,容易出现类膨胀,需要维护的代码就要多一些, 并且它们之间往往长得都很相似,比如java中的InputStream类,如果对这些装饰者类不熟悉的话,可能一时间会陷入不知道该使用哪个装饰者的尴尬境地。
何时应用装饰者模式:
当你需要扩展一个类的功能,或者给一个类动态的附加功能,又可以动态的撤销,或者想在一个方法的执行前/后添加自己的行为,都可以选择装饰者模式。
经典实战:对读入的内容字符转大写
public class UpperCaseInputStream extends FilterInputStream{
protected UpperCaseInputStream(InputStream in) {
super(in);
}
@Override
public int read() throws IOException {
int c = super.read();
return c==-1?c:Character.toUpperCase(c);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int result = super.read(b, off, len);
for(int i=0;i<result;i++){
b[i] = (byte) Character.toUpperCase((char)b[i]);
}
return result;
}
}
测试:
public class Tests {
public static void main(String[] args) throws IOException {
int c = 0;
try {
InputStream in = new UpperCaseInputStream
(new BufferedInputStream(new FileInputStream(new File("d:/data/aa.txt"))));
while((c=in.read())>0){
System.out.println((char)c);
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}