一、了解装饰者模式
1.1 什么是装饰者模式
装饰者模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰者来包裹真实的对象。
所以装饰者可以动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的方案。
1.2 装饰者模式组成结构
- 抽象构件 (Component):给出抽象接口或抽象类,以规范准备接收附加功能的对象。
- 具体构件 (ConcreteComponent):定义将要接收附加功能的类。
- 抽象装饰 (Decorator):装饰者共同要实现的接口,也可以是抽象类。
- 具体装饰 (ConcreteDecorator):持有一个 Component 对象,负责给构件对象“贴上”附加的功能。
1.3 装饰者模式 UML 图解
1.4 装饰者模式应用场景
- 需要扩展一个类的功能,或给一个类添加附加职责。
- 需要动态的给一个对象添加功能,这些功能可以再动态的撤销。
- 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。
- 当不能采用生成子类的方法进行扩充时。可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。
1.5 装饰者模式特点
- 装饰者对象和具体构件有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。
- 装饰者对象包含一个具体构件的引用(reference)。
- 装饰者对象接受所有来自客户端的请求。它把这些请求转发给具体构件。
- 装饰者对象可以在转发这些请求以前或以后动态增加一些功能。
二、装饰者模式具体应用
2.1 问题描述
星巴兹咖啡订单系统:星巴兹店提供了各式各样的咖啡,以及各种咖啡调料。为了适应饮料需求供应,所以让你设计一个更新订单系统。
2.2 使用继承
在购买咖啡时,可以要求在咖啡中添加各种调料,例如:豆浆 (Soy
)、摩卡 (Mocha
)、奶泡等。由于各种调料的价格不相同,所以订单系统必须要考虑这些因素。于是就有了下面的尝试
2.3 继承类图
这还只是列出了一部分的类,这简直是类爆炸,可以看出这样做显然是不行的。所以我们要慎用继承,尽量用组合和委托。
2.4 装饰者模式登场
装饰者模式涉及到的一个重要的设计原则 (当然还涉及到了其他的设计原则,比如多用组合,少用继承等):类应该对扩展开放,对修改关闭。
在设计过程中,我们允许类容易扩展,在不修改原有代码的情况下,就可以扩展新的行为。这样的设计具有弹性可以应对改变,可以接受新的功能来应对新的需求。
将装饰者模式应用到问题中去:假如我们想要摩卡和豆浆深焙咖啡,那么,要做的是:
- 拿一个深焙咖啡 (
DarkRoast
) 对象 - 以摩卡 (
Mocha
) 装饰它 - 以豆浆 (
Soy
) 装饰它 - 调用
cost()
方法,并依赖委托将调料的价钱加上去
(1) 装饰者模式设计图
(2)代码实现
饮料 Beverage
抽象类 (抽象构件)
package com.jas.decorator;
public abstract class Beverage {
String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
浓咖啡 Espresso
类 (具体构件)
package com.jas.decorator;
public class Espresso extends Beverage {
public Espresso(){
description = "Espresso ";
}
@Override
public double cost() {
return 1.99;
}
}
黑咖啡 HouseBlend
类 (具体构件)
package com.jas.decorator;
public class HouseBlend extends Beverage {
public HouseBlend(){
description = "House Blend Coffee ";
}
@Override
public double cost() {
return 0.80;
}
}
调料 CondimentDecorator
抽象类 (抽象装饰构件)
package com.jas.decorator;
public abstract class CondimentDecorator extends Beverage{
@Override
public abstract String getDescription();
}
摩卡 Mocha
类 (具体装饰构件)
package com.jas.decorator;
public class Mocha extends CondimentDecorator {
private Beverage beverage = null; //用一个实例变量来记录饮料,也就是被装饰者
public Mocha(Beverage beverage){
this.beverage = beverage; //通过构造函数将被装饰者实例化
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Mocha "; //用来加上调料,一起描述饮料
}
@Override
public double cost() {
return 0.2 + beverage.cost(); //计算摩卡饮料的价钱,为摩卡价钱 + 饮料价钱
}
}
豆浆 Soy
类 (具体装饰构件)
package com.jas.decorator;
public class Soy extends CondimentDecorator {
private Beverage beverage = null;
public Soy(Beverage beverage){
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Soy ";
}
@Override
public double cost() {
return 0.1 + beverage.cost();
}
}
测试代码 StarbuzzCoffee
类
package com.jas.decorator;
public class StarbuzzCoffee {
public static void main(String[] args) {
//简单要一杯浓咖啡
Beverage beverage1 = new Espresso();
System.out.println(beverage1.getDescription() + "$" + beverage1.cost());
//两份摩卡加一份豆浆的浓咖啡
Beverage beverage2 = new Espresso();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Soy(beverage2);
System.out.println(beverage2.getDescription() + "$" + beverage2.cost());
//一份摩卡加一份豆浆的黑咖啡
Beverage beverage3 = new HouseBlend();
beverage3 = new Mocha(beverage3);
beverage3 = new Soy(beverage3);
System.out.println(beverage3.getDescription() + "$" + beverage3.cost());
}
}
/**
* 输出
* Espresso $1.99
* Espresso , Mocha , Mocha , Soy $2.49
* House Blend Coffee , Mocha , Soy $1.1
*/
2.5 装饰者模式问题总结
- 装饰者与被装饰对象有相同的超类型 (
DarkRoast
与装饰类Mocha
和Soy
都继承自Beverage
(饮料))。 - 可以使用一个或多个装饰对象包装一个对象。
- 因为装饰者与被装饰者具有相同的超类型,所以在任何需要原始对象的情况下,都可以用装饰过的对象去代替它。
- 装饰者可以在所委托被装饰者的行为之前与之后,加上自己的行为,以达到特定的目的。
- 对象可以在任何时候被装饰,所以在运行时动态地、不限量地用你喜欢的装饰者去装饰对象。
三、真实世界的装饰者 Java I/O
3.1 了解 Java I/O 装饰者模式
在了解了装饰者模式之后,I/O 相关的类对你来说就更有意义了,因为这其中很多类都是装饰者。比如下面相关的类
装饰 I/O 类
3.2 自定义 Java I/O 装饰者
问题描述:读取文件,把输入流内的所有大写字符转为小写。
装饰者 LowerCaseInputStream
类
package com.jas.decorator;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 扩展 FilterInputStream,这是所有 InputStream 的抽象装饰者
*/
public class LowerCaseInputStream extends FilterInputStream {
public LowerCaseInputStream(InputStream inputStream){
super(inputStream);
}
@Override
public int read() throws IOException{
int c = super.read();
return (c == -1 ? c : Character.toLowerCase((char)c));
}
@Override
public int read(byte[] bytes, int offset, int len) throws IOException{
int result = super.read(bytes, offset, len);
for (int i = offset; i < offset + result; i++) {
bytes[i] = (byte) Character.toLowerCase((char)bytes[i]);
}
return result;
}
}
测试 InputTest
类
package com.jas.decorator;
import java.io.*;
public class InputTest {
public static void main(String[] args) {
int c = 0;
InputStream in = null;
try {
//设置 FileInputStream ,先用 BufferedInputStream 装饰它,再用 LowerCaseInputStream 进行装饰
in = new LowerCaseInputStream(
new BufferedInputStream(
new FileInputStream("test.txt")));
while ((c = in.read()) >= 0){
System.out.print((char)c);
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**在文件中为“HELLO WORLD”
*
* 输出
* hello world
*/
四、装饰者模式总结
4.1 装饰者模式的优缺点
优点
- Decorator 模式与继承关系的目的都是要扩展对象的功能,但是 Decorator 可以提供比继承更多的灵活性。
- 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计者可以创造出很多不同行为的组合。
缺点
- 这种比继承更加灵活机动的特性,也同时意味着更加多的复杂性。
- 装饰模式会导致设计中出现许多小类 (I/O 类中就是这样),如果过度使用,会使程序变得很复杂。
- 装饰模式是针对抽象组件(Component)类型编程。但是,如果你要针对具体组件编程时,就应该重新思考你的应用架构,以及装饰者是否合适。
参考资料
《Head First 设计模式》
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。