Design Principle: Classes should be open for extension, but closed for modification.
The Decorator Pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. 装饰器模式可以让你的对象动态添加特性
引子 Starbuzz 设计了一款软件卖咖啡,但是设计太烂了,导致类膨胀了。如果是你你会怎么整?
原始设计, 有一个基类 Beverage,然后各种子类比如浓缩咖啡,美式等。但是光咖啡还不够,我们还可以加入各种调味料,比如抹茶,奶盖等。每加入新的调料都是一种新的类,比如浓缩+抹茶。类数量成指数上升
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 +-----------------+ | Beverate | |---------------- | | description | |---------------- | | getDescription()| | cost() | | | +-----------------+ ^ ^ ^ | | |--------------- | | | +--------------+ +--------------+ | | Espreesso | | DarkRoast | | |--------------| |--------------| ... | cost() | | cost() | +--------------+ +--------------+ ^ ^ | | | | +-----------------+ | |EspreessoWithMilk| | |-----------------| ... | cost() | +-----------------+
随之设计师又想到了另一种解决方案,可以将所有的属性和调味品设置成属性放到基类中,然后通过 flag 知道是否含有某种调味品,然后在子类中通过设置这些 flag 的值,定制 cost 结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 +----------------+ | Beverage | |----------------| | description | | soy | | mocha | | ... | |----------------| | hasSoy() | | hasMocha() | | ... | | | | | | | | | | | +----------------+
对应的代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Beverage { public double cost () { double condimentCost = 0.0 ; if (hasMilk()) { codimentCost += milkCost; } if (hasSoy()) { } } } public class DarkRoast extends Beverage { public double cost () { return 1.00 + super .cost(); } }
这样做虽然避免的类爆炸式增长,但是导致了新的问题。比如每当调料价格变动,你就必须得改变老得代码。新加调料,你还得修改之前的 if 逻辑。而且如果有新的饮料,比如茶,那么这个继承关系在逻辑层面上就不是很合理了。为了解决类似的问题,我们引入装饰者模式
UML
每个 Component 可以自己调用自己,也可以被 Decorator 包裹
每个 Decorator 都持有一个 Component 的引用
ConcrateComponent 是 Component 的具体实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 +--------------+ | Component | |--------------|------------------| | +operation() | | | | | +--------------+ | ^ ^ | | | | | | | +---------------------+ +--------------+ | | ConcreateComponent | | Decorator |<>---| |---------------------| |--------------| | +operation() | | +operation() | | | | | +---------------------+ +--------------+ ^ ^ | | | | +---------------------+ +---------------------+ | ConcreateDecoratorA | | ConcreateDecoratorA | |---------------------| |---------------------| | +operation() | | +operation() | | +addBehavior() | | +addBehavior() | +---------------------+ +---------------------+
实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 public abstract class Beverage { String description = "Unknown Beverage" ; public String getDescription () { return description; } public abstract double cost () ; } public class Espresso extends Beverage { public Espresso () { description = "Espresso" ; } @Override public double cost () { return 1.99 ; } } public abstract class CondimentDecorator extends Beverage { @Override public abstract String getDescription () ; } public class Mocha extends CondimentDecorator { Beverage beverage; public Mocha (Beverage beverage) { this .beverage = beverage; } @Override public double cost () { return beverage.cost() + .20 ; } @Override public String getDescription () { return beverage.getDescription() + ", Mocha" ; } } public class Soy extends CondimentDecorator { Beverage beverage; public Soy (Beverage beverage) { this .beverage = beverage; } @Override public double cost () { return beverage.cost() + .15 ; } @Override public String getDescription () { return beverage.getDescription() + ", Soy" ; } } public class Client { public static void main (String[] args) { Beverage myBeverage = new Mocha(new Soy(new Espresso())); System.out.println(myBeverage.cost()); System.out.println(myBeverage.getDescription()); } }
问,我现在如果加了双份的抹茶,description 输出时会现实 Mocha, Mocha。那如果我想要他输出 Double Mocha 的话需要怎么做?
按照装饰模式的思路,可以将 description 的实现改为容器,比如 list, 然后在最外层加入一个 CustomizedDescDecorator 截取 description 做整合
该模式在 JDK 中的应用 Java 的 I/O 包就使用了装饰器模式。IO 分两种,字节流(Input/OutputStream)和字符流(Reader/Writer)。
以输入字节流 InputStream 为例,继承关系如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 +-------------+ | InputStream | +-------------+ ^ ------------------------------|------------------------------------------------------ | | | | | | | | | | +-----------------+ +-------------------+ +-------------------+ +----------------------+ | | FileInputStream | | FilterInputStream | | ObjectInputStream | | ByteArrayInputStream | ... +-----------------+ +-------------------+ +-------------------+ +----------------------+ ^ --------------------------|----------------------------------------------- | | | | | | | | +---------------------+ +---------------------+ +--------------------+ | | BufferedInputStream | | DataInputStream | | PushbakInputStream | ... +---------------------+ +---------------------+ +--------------------+
一开始看岔了,把 FilterInputStream 和 FileInputStream 看成同一个了,所以没能把它和装饰模式匹配起来。在 IO 的实现中,FileInputStream, ObjectInputStream 等即对饮了 ConcreateComponent, 是具体实现。
FilterInputStream 对应了 Decorator, 是修饰器的基类,持有了 inputStream 的引用,而 BufferedInputStream 则为装饰器的具体实现,起到包装 Component 的作用。
BufferedInputStream 使用示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class IoDecoratorDemo { public static void main (String[] args) { ClassLoader classloader = Thread.currentThread().getContextClassLoader(); InputStream is = classloader.getResourceAsStream("c1_2.xml" ); try (BufferedInputStream bis = new BufferedInputStream(is)) { byte data; while ((data = (byte ) bis.read()) != -1 ) { System.out.print((char ) data); } System.out.println("Finish read..." ); } catch (IOException e) { e.printStackTrace(); } } }