文章目录
定义
装饰者(Decorator)模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
案例1:三点几啦
更新咖啡连锁店的订单系统,原先类的设计:
咖啡店为拓展业务,允许顾客在饮料上添加各种调料,如:
- 蒸奶 Steamed Milk
- 豆浆 Soy
- 摩卡(巧克力风味) Mocha
- 覆盖奶泡
加入的调料收取不同的费用。
首次尝试
类数量爆炸
这违背严重两条设计原则:
- 多用组合,少用继承。
- 为了交互对象之间的松耦合设计而努力。
再次尝试
利用实例变量和继承,追踪这些调料:
这次尝试的局限性:
- 调料价钱的改变会使我们更改现有代码。
- 一旦出现新的调料,我们就需要加上新的方法,并改变超类中的cost()方法。
- 以后可能会开发出新饮料。对这些饮料而言(例如:冰茶),某些调料可能并不适合,但是在这个设计方式中,Tea(茶)子类仍将继承那些不适合的方法,例如:hasWhip()(加奶泡)。
- 万一顾客想要双倍摩卡咖啡,怎么办?
设计原则:类应该对扩展开放,对修改关闭
我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为。
如能实现这样的目标,这样的设计具有弹性可以应对改变,可以接受新的功能来应对改变的需求。
虽然似乎有点矛盾,但是的确有一些技术可以允许在不直接修改代码的情况下对其进行扩展。
在选择需要被扩展的代码部分时要小心。每个地方都采用开放-关闭原则,是一种浪费,也没必要,还会导致代码变得复杂且难以理解。
(MyNote:随机应变)
尝用装饰者模式
我们要以饮料为主体,然后在运行时以调料来“装饰”(decorate)饮料。比方说,如果顾客想要摩卡
和奶泡深焙咖啡,那么,要做的是:
1.拿一个深焙咖啡(DarkRoast)对象
2.以摩卡(Mocha)对象装饰它
3.以奶泡(Whip)对象装饰它
4.调用cost()方法,并依赖委托(delegate)将调料的价
钱加上去。
装饰者模式特征
-
装饰者和被装饰对象有相同的超类型。
-
你可以用一个或多个装饰者包装一个对象。
-
既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。
-
装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。
-
对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。
本例的类图
放码过来
饮料类
public abstract class Beverage {
String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
HouseBlend
public class HouseBlend extends Beverage {
public HouseBlend() {
description = "House Blend Coffee";
}
public double cost() {
return .89;
}
}
DarkRoast
public class DarkRoast extends Beverage {
public DarkRoast() {
description = "Dark Roast Coffee";
}
public double cost() {
return .99;
}
}
Espresso
public class Espresso extends Beverage {
public Espresso() {
description = "Espresso";
}
public double cost() {
return 1.99;
}
}
Decaf
public class Decaf extends Beverage {
public Decaf() {
description = "Decaf Coffee";
}
public double cost() {
return 1.05;
}
}
调料装饰类
public abstract class CondimentDecorator extends Beverage {
Beverage beverage;
public abstract String getDescription();
}
Milk
public class Milk extends CondimentDecorator {
public Milk(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Milk";
}
public double cost() {
return .10 + beverage.cost();
}
}
Mocha
public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Mocha";
}
public double cost() {
return .20 + beverage.cost();
}
}
Soy
public class Soy extends CondimentDecorator {
public Soy(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Soy";
}
public double cost() {
return .15 + beverage.cost();
}
}
Whip
public class Whip extends CondimentDecorator {
public Whip(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Whip";
}
public double cost() {
return .10 + beverage.cost();
}
}
运行测试类
public class StarbuzzCoffee {
public static void main(String args[]) {
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription()
+ " $" + beverage.cost());
System.out.println("---");
Beverage beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDescription()
+ " $" + beverage2.cost());
System.out.println("---");
Beverage beverage3 = new HouseBlend();
beverage3 = new Soy(beverage3);
beverage3 = new Mocha(beverage3);
beverage3 = new Whip(beverage3);
System.out.println(beverage3.getDescription()
+ " $" + beverage3.cost());
}
}
运行结果:
Espresso $1.99
---
Dark Roast Coffee, Mocha, Mocha, Whip $1.49
---
House Blend Coffee, Soy, Mocha, Whip $1.34
案例2:编写自己的Java I/0装饰者
JDK中的IO流概述
java.io包内的类中许多类都是装饰者。
下面是一个典型的对象集合,用装饰者来将功能结合起来,
以读取文件数据:
BufferedInputStream及LineNumberInputStream都扩展自FilterInputStream,而FilterInputStream是一个抽象的装饰类。
java.io其实没有多大的差异。我们把java.io API范围缩小,让你容易查看它的文件,并组合各种“输入”流装饰者来符合你的用途。
你会发现输出流(OutputStream)的设计方式也是一样的。你可能还会发现Reader/Writer流(作为基于字符数据的输入输出)和输入流/输出流的类相当类似(虽然有一些小差异和不一致之处,但是相当雷同,所以你应该可以了解这些类)。
但是Java I/O也引出装饰者模式的一个缺点:利用装饰者模式,常常造成设计中有大量的小类,数量实在太多,可能会造成使用此API程序员的困扰。
但是,现在你已经了解了装饰者的工作原理,以后当使用别人的大量装饰的API时,就可以很容易地辨别出他们的装饰者类是如何组织的,以方便用包装方式取得想要的行为。
放码过来
这个想法怎么样:编写一个装饰者,把输入流内的所有大写字符转成小写。
举例:当读取“I know the Decorator Pattern thereforeI RULE!”,装饰者会将它转成“i know thedecorator pattern therefore i rule ! ”
LowerCaseInputStream
扩展FilterInputStream,这是所有InputStream的抽象装饰者:
import java.io.*;
public class LowerCaseInputStream extends FilterInputStream {
public LowerCaseInputStream(InputStream in) {
super(in);
}
public int read() throws IOException {
int c = in.read();
return (c == -1 ? c : Character.toLowerCase((char)c));
}
public int read(byte[] b, int offset, int len) throws IOException {
int result = in.read(b, offset, len);
for (int i = offset; i < offset+result; i++) {
b[i] = (byte)Character.toLowerCase((char)b[i]);
}
return result;
}
}
创建一测试文件D:\test.txt
I know the Decorator Pattern therefore I RULE!
运行测试类
import java.io.*;
public class InputTest {
public static void main(String[] args) throws IOException {
int c;
InputStream in = null;
try {
in =
new LowerCaseInputStream(
new BufferedInputStream(
new FileInputStream("D:\\text.txt")));
while((c = in.read()) >= 0) {
System.out.print((char)c);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) { in.close(); }
}
System.out.println();
System.out.println("---");
//
try (InputStream in2 =
new LowerCaseInputStream(
new BufferedInputStream(
new FileInputStream("D:\\text.txt"))))
{
while((c = in2.read()) >= 0) {
System.out.print((char)c);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
i know the decorator pattern therefore i rule!
---
i know the decorator pattern therefore i rule!