装饰器模式的定义
装饰器模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
如下:
ConcreteComponent
是我们将要动态地加上新行为的对象,它扩展自Component
;
- 每个装饰者
Decorator
都有一个(包装一个)组件对象,也就是说,装饰者有一个实例变量保存某个Component
是的引用;
- 每个具体的装饰者可以加上新的方法。新行为是通过在旧行为前面或后面做一些计算来添加的;
场景
星巴兹咖啡店的订单系统
场景介绍
星巴兹咖啡店扩展迅速,他们准备更新订单系统,以合乎他们的饮料供应要求。类设计原来是这样的:a)饮料类Beverage
是一个抽象类,店内所有提供的饮料都要继承此类;b)cost()
方法是抽象的,子类必须定义自己的实现,以实现对每种饮料计算价格;c)购买咖啡时,也可以加入各种调料,例如:蒸奶、豆浆、摩卡或覆盖奶泡;星巴兹会根据所加入的调料收取不同的费用。
采用这样的设计,他们的类将会爆炸:
解决方案
这里采用的做法是:以饮料为主体,在运行时以调料来“装饰”饮料。比如,如果顾客想要摩卡和奶泡深焙咖啡,那么需要这样做:
- 拿一个深焙咖啡
DarkRoast
对象;
- 以摩卡
Mocha
对象装饰它;
- 以奶泡
Whip
对象再进行装饰它;
- 调用
cost()
方法,并依赖委托将调料的价钱加上去;
1 2 3 4 5 6 7 8 9
| public abstract class Beverage { String description = "Unknown Beverage";
public String getDescription() { return description; }
public abstract double cost(); }
|
实现一些饮料,我们先实现浓缩咖啡Espresso
和综合咖啡HouseBlend
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class Espresso extends Beverage { public Espresso() { description = "Espresso"; } public double cost() { return 1.99; } }
public class HouseBlend extends Beverage { public HouseBlend() { description = "House Blend Coffee"; }
public double cost() { return .89; } }
|
调料抽象类:
1 2 3 4 5
| public abstract class CondimentDecorator extends Beverage { Beverage beverage;
public abstract String getDescription(); }
|
实现摩卡口味的咖啡:a)getDescription()
中加上摩卡的描述,b)cost()
如中加上了摩卡的费用,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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(); } }
|
我们来使用一下新的订单系统:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class StarbuzzCoffee { public static void main(String args[]) { Beverage beverage = new Espresso(); System.out.println(beverage.getDescription() + " $" + beverage.cost());
Beverage beverage2 = new DarkRoast(); beverage2 = new Mocha(beverage2); beverage2 = new Mocha(beverage2); beverage2 = new Whip(beverage2); System.out.println(beverage2.getDescription() + " $" + beverage2.cost()); Beverage beverage3 = new HouseBlend(); beverage3 = new Soy(beverage3); beverage3 = new Mocha(beverage3); beverage3 = new Whip(beverage3); System.out.println(beverage3.getDescription() + " $" + beverage3.cost()); } }
|
Java中的装饰者
Java I/O
InputStream
是抽象组件;
FileInputStream
、StringBufferInputStream
、ByteArrayInputStream
是可以被装饰者包起来的具体组件,还有少数类没有被列出,如ObjectInputStream
;
PushbackInputStream
、BufferedInputStream
、DataInputStream
、InflatorInputStream
、ZipInputStream
是具体的装饰者;
- Java I/O也引出装饰者模式的一个“缺点”:利用装饰者模式,常常造成设计中大量的小类,数量实在太多,可能会造成使用此API的程序员困扰
实现输入流中所有大写字符转成小写的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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; } }
|
参考
- https://wickedlysmart.com/head-first-design-patterns/