1.概述
- 动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码 & 减少子类个数)。 ——《设计模式》GoF
-
1.1动机
- 在某些情况下我们可能会"过度地使用继承来扩展对象的功能"由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。
- 如何使"对象功能的扩展"能够根据需要来动态地实现 ? 同时避免"扩展功能的增多"带来的子类膨胀问题, 从而使得任何"功能扩展变化""所导致的影响将为最低 ?
1.2结构
- ![E2P9H4U7[_3PV)[OLO$)HWC.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1628236272666-d31386c3-3c57-407e-94ef-0938ef6f4da9.png#clientId=u32ed8832-7378-4&from=paste&height=215&id=ub9b7503c&margin=%5Bobject%20Object%5D&name=E2P9H4U7%5B_3PV%29%5BOLO%24%29HWC.png&originHeight=429&originWidth=966&originalType=binary&ratio=1&size=94197&status=done&style=none&taskId=u7daaaf6a-7623-4ca4-9087-dbe7040a14f&width=483)
2.要点总结
- 通过采用组合而非继承的手法, Decorator模式 实现了在运行时动态扩展对象功能的能力. 而且可以根据需要扩展多个功能. 避免了使用继承带来的 “灵活性差” 和 “多子类衍生问题”
- Decorator类 在接口上表现为 is-a Component 的继承关系, 即Decorator类 继承了 Component类所具有的接口. 但又在实现上表现为 has-a Component 的组合关系, 即 Decorator类 又使用了另一个Component类
- Decorator模式的 目的并非解决 “多子类衍生的多继承”问题, 该模式应用的要点在于解决 “主体类在多个方向上的扩展功能” — 是为”装饰”的含义
3.案例
星巴克咖啡订单项目:
类图
代码
// 被装饰者: 抽象主体, 缓冲层, 具体主体 ```java /**
- @description: TODO 被装饰的抽象主体, 缓冲层, 具体主体 */ public abstract class Drink {
public String description; // 描述 private float price = 0.0F;// 价格
/**
- @description: 计算费用的抽象方法,供子类实现
- @return: float 当前(主体+装饰者)的状态(价格) */ public abstract float cost();
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public float getPrice() { return price; }
public void setPrice(float price) { this.price = price; } }
/**
@description: 缓冲层 */ class Coffee extends Drink{
@Override public float cost() {
return super.getPrice();
} }
// ————— 以下是具体主类 ———————-
class ShortBLack extends Coffee{ ShortBLack(){ setDescription(“shortBlack 4.0”); setPrice(4.0F); } }
class Espresso extends Coffee{ Espresso(){ setDescription(“espresso 5.0”); setPrice(5.0F); } }
class LongBlack extends Coffee{ public LongBlack(){ setDescription(“longBlack 6.0”); setPrice(6.0F); } }
/**
- @description: 待新增的类, 供后续测试 */ class Decaf{
}
- // 装饰者: 装饰者类及其子类
```java
/**
* @description: TODO 装饰者(继承结构)
*/
public class Decorator extends Drink{
private Drink obj;
public Decorator(Drink obj){ // 组合
this.obj = obj;
}
@Override
public float cost() {
// 操作 装饰者自身 + 具体主体
return super.getPrice() + obj.cost();
}
@Override
public String getDescription() {
// 查看验证
return description + " " + getPrice() + " && " + obj.getDescription();
}
}
// -------------- 以下是具体子装饰类 --------------
class Chocolate extends Decorator{
public Chocolate(Drink obj){
super(obj);
setDescription(" Chocolate ");
setPrice(3.0F);
}
}
class Milk extends Decorator{
public Milk(Drink obj){
super(obj);
setDescription(" Milk ");
setPrice(4.0F);
}
}
class Soy extends Decorator{
Soy(Drink obj){
super(obj);
setDescription(" Soy ");
setPrice(5.0F);
}
}
- // 测试类 ```java
public class CoffeeBar {
public static void main(String[] args) {
// TODO Auto-generated method stub
// 装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack
// 1. 点一份 LongBlack
Drink order = new LongBlack();
System.out.println("费用1=" + order.cost());
System.out.println("描述=" + order.getDescription());
// 2. order 加入一份牛奶
order = new Milk(order);
System.out.println("order 加入一份牛奶 费用 =" + order.cost());
System.out.println("order 加入一份牛奶 描述 = " + order.getDescription());
// 3. order 加入一份巧克力
order = new Chocolate(order);
System.out.println("order 加入一份牛奶 加入一份巧克力 费用 = " + order.cost());
System.out.println("order 加入一份牛奶 加入一份巧克力 描述 = " + order.getDescription());
// 3. order 加入一份巧克力
order = new Chocolate(order);
System.out.println("order 加入一份牛奶 加入2份巧克力 费用 =" + order.cost());
System.out.println("order 加入一份牛奶 加入2份巧克力 描述 = " + order.getDescription());
System.out.println("===========================");
/* Drink order2 = new DeCaf();
System.out.println("order2 无因咖啡 费用 =" + order2.cost());
System.out.println("order2 无因咖啡 描述 = " + order2.getDescription());
order2 = new Milk(order2);
System.out.println("order2 无因咖啡 加入一份牛奶 费用 =" + order2.cost());
System.out.println("order2 无因咖啡 加入一份牛奶 描述 = " + order2.getDescription());*/
}
}
- // 打印
费用1=6.0 描述=longBlack 6.0 order 加入一份牛奶 费用 =10.0 order 加入一份牛奶 描述 = Milk 4.0 && longBlack 6.0 order 加入一份牛奶 加入一份巧克力 费用 = 13.0 order 加入一份牛奶 加入一份巧克力 描述 = Chocolate 3.0 && Milk 4.0 && longBlack 6.0 order 加入一份牛奶 加入2份巧克力 费用 =16.0
order 加入一份牛奶 加入2份巧克力 描述 = Chocolate 3.0 && Chocolate 3.0 && Milk 4.0 && longBlack 6.0
<a name="wXvRB"></a>
# 5.经典使用
---
<a name="b5SbS"></a>
## 5.1JDK中IO流
<a name="uoO3g"></a>
#### 5.1.1两种设计方案的对比
- 使用继承的硬编码(对继承的不良使用)
- 类数量: 1+n+n*m!/2
- ![9KZ(SAE`$FJ%P)8)~HN`W9V.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1628240376023-615ddd04-cd6d-4519-a570-5474992dd91c.png#clientId=u33f78a30-94f6-4&from=drop&height=356&id=GeEgs&margin=%5Bobject%20Object%5D&name=9KZ%28SAE%60%24FJ%25P%298%29~HN%60W9V.png&originHeight=712&originWidth=1417&originalType=binary&ratio=1&size=268943&status=done&style=none&taskId=u3684354d-5eef-4574-bec6-ba0fa582ac3&width=709)
- 使用装饰者模式
- 类数量: 1+n+1+m
- ![捕获.PNG](https://cdn.nlark.com/yuque/0/2021/png/12524106/1628249253450-f333d5ff-18a0-48db-a13c-ca39d1c5af0e.png#clientId=u0d062971-ceed-4&from=drop&height=298&id=ufb0b263e&margin=%5Bobject%20Object%5D&name=%E6%8D%95%E8%8E%B7.PNG&originHeight=595&originWidth=1257&originalType=binary&ratio=1&size=163746&status=done&style=none&taskId=uc79c2948-4222-4a4d-961e-a1a34194132&width=629)
<a name="O0Vt7"></a>
#### 5.1.2结构
![IO.PNG](https://cdn.nlark.com/yuque/0/2021/png/12524106/1625671900525-6c7717bf-e9f7-4026-a484-f3cf65f6e77f.png#clientId=u9259a46a-c4df-4&from=ui&id=u93d23048&margin=%5Bobject%20Object%5D&name=IO.PNG&originHeight=585&originWidth=1854&originalType=binary&ratio=1&size=21574&status=done&style=none&taskId=u340e5454-0e1a-42fe-a8d8-825270d1dd1)
<a name="A3nUu"></a>
#### 5.1.3伪码
```cpp
//扩展的操作: 在基础流上的加密操作
class CryptoStream: public Stream {
Stream* stream;//...
public:
CryptoStream(Stream* stm):stream(stm){
}
virtual char Read(int number){
//额外的加密操作...
stream->Read(number);//读文件流
}
virtual void Seek(int position){
//额外的加密操作...
stream::Seek(position);//定位文件流
//额外的加密操作...
}
virtual void Write(byte data){
//额外的加密操作...
stream::Write(data);//写文件流
//额外的加密操作...
}
};
5.1.4分析及总结
如上的C++扩展的加密流的伪码分析:
1.继承自Stream, 以之加密的操作能配合Stream抽象基类的读写操作(后续由具体的网络流/缓冲流实现)
2.参数化Stream, 接收对应的实际需要加密的流类型(如上具体的网络流/缓冲流), 以抽象化加密接口.
否则: 对应的网络流/缓冲流, 都需要一个具体的 "加密网络流"/"加密缓冲流", 导致类爆炸
3.由编译时装配, 改善成运行时 (组合的方式)装配
4.(实例教程中)由于所有的扩展类都需要一个Stream成员变量, 所以可以将这个通有Stream抽象到上层.
而若抽象到基类Stream中, 基类本身并不需要Stream变量, 不符合单一职责.
故可新建一个二者之间的缓冲层来存放这个子类通用的Stream变量.
5.Java中:
InputStream基类的直接实现子类FilterInputStream, 即如上抽象层作用.
其包含一个成员变量{ protected volatile InputStream in; }
JDK对 FilterInputStream类 描述:
FilterInputStream包含一些其他输入流,它用作其基本的数据源,可能会沿途转换数据或提供附加功能。
FilterInputStream本身简单地覆盖了所有InputStream的方法, InputStream版本将所有请求传递给包含的输入流。
FilterInputStream可以进一步覆盖这些方法中的一些,并且还可以提供附加的方法和领域。
总结:
FilterInputStream类 即是父类装饰者, 其具体子类(BufferedInputStream等)即具体装饰者, 扩展了缓冲/BASE编码等功能.
FileInputStream类 即父类组件, 其具体子类即具体组件.
(虽然装饰者基类直接装饰的变量类型是InputStream, 但通常都操作 FileInputStream )
(对于OutputStream/Reader/Write同理. Reader基类的直接抽象子类FilterReader, 也包含了一个 Reader 类型的成员变量)
案例对比IO源码分析:
//1. InputStream是抽象类,类似我们前面讲的Drink
//2. FileInputStream是InputStream 子类,类似我们前面的DeCaf, LongBlack
//3. FilterInputStream是InputStream 子类:类似我们前面的Decorator 修饰者
//4. DataInputStream是FilterInputStream 子类,具体的修饰者,类似前面的Milk, Soy等
//5. FilterInputStream类有protected volatile InputStream in;即含被装饰者
//6.分析得出在 JDK-IO 体系中,就是使用装饰者模式