Header First设计模式——装饰者模式

       我曾经以为应该用继承处理一切。后来领教到运行时扩展,远比编译时期的继承威力大。本章可以称为“给爱用继承的人一个全新的设计眼界”。我们即将再度探讨典型的继承滥用问题。在本章将会学到如何使用对象组合的方式,做到在运行时装饰类。一旦你熟悉了装饰的技巧,你将能在不修改任何底层代码的情况下,给你的(或别人的)对象赋予新的职责。
       新的例题:星巴兹是以狂战速度最快而闻名的咖啡连锁店。因为扩张速度实在太快,他们准备更新订单系统,以合乎他们的饮料供应要求。他们原来的类设计是这样:
Header First设计模式——装饰者模式

购买咖啡时,也可以要求在其中加入各种调料,例如:蒸奶,豆浆,摩卡或覆盖奶泡。星巴兹会根据所加入的调料收取不同的费用。所以订单系统必须考虑到这些调料部分。这是他们的第一个尝试:

Header First设计模式——装饰者模式

 

接着,我们可能会想到继承,在基类Beverage中,加上实例变量代表是否加上调料。但是这样也会带来一些问题,例如:
1、一旦出现新的调料,我们就需要添加新的方法,并改变超类中cost()方法。

2、对于一些饮料,可能并不希望继承别的饮料方法。

因此,利用继承无法完全解决问题,在星巴克遇到的问题有:类数量爆炸、设计死板、以及基类加入的新功能并不适用于所有的子类。为了不违背开放—关闭原则:类应该对扩展开放,对修改关闭。所以在这里我们要以饮料为主体,然后在运行时以调料来“装饰”饮料,那么要做的是:

1:拿一个深焙咖啡(DarkRoast)对象。

2:以摩卡(Mocha)对象装饰它。

3:以奶泡(Whip)对象装饰它。

4:调用cost()方法,并依赖委托(delegate)将调料的价钱加上去。

但是如何“装饰”一个对象,而“委托”又要如何与此搭配使用?

以装饰者构造饮料订单
1:以DarkRoast对象开始。DarkRoast继承自Beverage,且有一个用来计算饮料价钱的cost()方法。
2:顾客想要摩卡(Mocha),所以建立一个Mocha对象,并用它将DarkRoast对象包(wrap)起来。Mocha对象是一个装饰者,他的类型“反应”了它所装饰的对象(本例中就是Beverage)。所谓的“反应”,指的就是两者类型一致。
3:顾客也想要奶泡(Whip),所以需要建立一个Whip装饰者,并用它将Mocha对象包装起来。别忘了,DarkRoast继承自Beverage,并有一个cost()方法,用来计算饮料价钱。所以,被Mocha和Whip包起来的DarkRoast对象仍然可以具有DarkRoast的一切行为,包括调用它的cost()方法。
4:现在,该是顾客算钱的时候了,通过调用最外圈装饰者(Whip)的cost()就可以办得到。Whip的cost()会先委托它装饰的对象(也就是Mocha)计算出价钱,然后再加上奶泡的价钱。
到此。我们知道了:
1.装饰者和被装饰对象有相同的超类型。
2.你可以用一个或多个装饰者包装一个对象。
3.几人装饰者和被装饰者对象有相同的超类型,所有在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。
4.装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。(关键点)
5.对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。
定义装饰者模式:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
看看它的类图:

Header First设计模式——装饰者模式

装饰者我们的饮料
让星巴兹饮料也能符合此框架
Header First设计模式——装饰者模式

 

具体实现:

从饮料下手,将饮料作为抽象类:

package com.wk.cafe;
 
public abstract class Beverage {
	String description = "Unkown Beverage";
	
	public String getDescription(){
		return description;
	}
	
	public abstract double cost();
}

调料抽象类,也就是装饰者类:

package com.wk.cafe;
 
public abstract class CondimentDecorator extends Beverage{
	public abstract String getDescription();
	
}

实现具体饮料:

package com.wk.cafe;
 
public class Espresso extends Beverage {
	public Espresso(){
		description = "Espresso";
	}
 
	public double cost(){
		return 1.99;
	}
}
package com.wk.cafe;
 
public class HouseBlend extends Beverage {  
    public HouseBlend() {  
        description = "HouseBlend";  
    }  
  
    public double cost() {  
        return 0.89;  
    }  
}  

实现具体装饰者类:

package com.wk.cafe;
 
public class Soy extends CondimentDecorator {
	Beverage beverage;
	public Soy(Beverage beverage){
		this.beverage = beverage;
	}
	public String getDescription(){
		return beverage.getDescription()+",Soy";
	}
 
	public double cost(){
		return 0.20+beverage.cost();
	}
}
package com.wk.cafe;
 
public class Whip extends CondimentDecorator {
	Beverage beverage;
	public Whip(Beverage beverage){
		this.beverage = beverage;
	}
	public String getDescription(){
		return beverage.getDescription()+",Whip";
	}
 
	public double cost(){
		return 0.20+beverage.cost();
	}
}
package com.wk.cafe;
 
public class Mocha extends CondimentDecorator {
	Beverage beverage;
	public Mocha(Beverage beverage){
		this.beverage = beverage;
	}
	public String getDescription(){
		return beverage.getDescription()+",Mocha";
	}
 
	public double cost(){
		return 0.20+beverage.cost();
	}
}

测试代码:

package com.wk.cafe;
 
public class StartbuzzCoffee {  
    public static void main(String args[]) {  
        Beverage beverage1 = new Espresso();  
        System.out.println(beverage1.getDescription() + "  $"  
                + beverage1.cost());  
        
        beverage1 = new Mocha(beverage1);
        beverage1 = new Whip(beverage1);
        System.out.println(beverage1.getDescription() + "  $"  
                + beverage1.cost());  
        
        Beverage beverage2 = new HouseBlend();  
        beverage2 = new Soy(beverage2);  
        beverage2 = new Mocha(beverage2);  
        beverage2 = new Whip(beverage2);  
        System.out.println(beverage2.getDescription() + "  $"  
                + beverage2.cost());  
    }  
  
} 

测试结果:

Header First设计模式——装饰者模式

 

java中的装饰者java.io类

Header First设计模式——装饰者模式

Java I/O引出装饰者模式的一个“缺点”:利用装饰者模式,会造成设计中存在大量的小类。

我们可以编写自己的Java I/O装饰者(即ConcreteDecorator),它的作用是把输入流中的所有小写字母转成大写:

代码如下:

package com.wk.cafe;
 
import java.io.FilterInputStream;  
import java.io.IOException;  
import java.io.InputStream;  
  
public class UpperCaseInputStream extends FilterInputStream {  
  
    protected UpperCaseInputStream(InputStream in) {  
        super(in);  
        // TODO Auto-generated constructor stub  
    }  
  
    public int read() throws IOException {  
        int c = super.read();  
        return (c == -1 ? c : Character.toUpperCase((char) c));  
    }  
  
    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.toUpperCase((char) b[i]);  
        }  
        return result;  
    }  
}  

测试代码:

package com.wk.cafe;
 
import java.io.*;  
 
public class UpperCaseInputStreamTest {  
    public static void main(String[] args) throws IOException {  
        int c;  
  
        try {  
            InputStream in = new UpperCaseInputStream(new BufferedInputStream(  
                    new FileInputStream("E:\\inputtest.txt")));  
  
            while ((c = in.read()) >= 0) {  
                System.out.print((char) c);  
            }  
  
            in.close();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
} 

运行结果:

Header First设计模式——装饰者模式