1.概述

  • 动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码 & 减少子类个数)。 ——《设计模式》GoF
  • 核心: 单一职责原则 + 合成复用原则

    1.1动机

    1. - 在某些情况下我们可能会"过度地使用继承来扩展对象的功能"由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。
    2. - 如何使"对象功能的扩展"能够根据需要来动态地实现 ? 同时避免"扩展功能的增多"带来的子类膨胀问题, 从而使得任何"功能扩展变化""所导致的影响将为最低 ?

    1.2结构

    1. - ![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.要点总结

    1. 通过采用组合而非继承的手法, Decorator模式 实现了在运行时动态扩展对象功能的能力. 而且可以根据需要扩展多个功能. 避免了使用继承带来的 “灵活性差” 和 “多子类衍生问题”
    2. Decorator类 在接口上表现为 is-a Component 的继承关系, 即Decorator类 继承了 Component类所具有的接口. 但又在实现上表现为 has-a Component 的组合关系, 即 Decorator类 又使用了另一个Component类
    3. Decorator模式的 目的并非解决 “多子类衍生的多继承”问题, 该模式应用的要点在于解决 “主体类在多个方向上的扩展功能” — 是为”装饰”的含义

      3.案例

  • 星巴克咖啡订单项目:

    • 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡);调料:Milk、Soy(豆浆)、Chocolate;
    • 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便;
    • 使用OO的来计算不同种类咖啡的费用:客户可以点单品咖啡,也可以单品咖啡+调料组合。

      4.使用模式

      方案

image.png

类图

点击查看【processon】

代码

  • // 被装饰者: 抽象主体, 缓冲层, 具体主体 ```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() {

    1. 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{

}

  1. - // 装饰者: 装饰者类及其子类
  2. ```java
  3. /**
  4. * @description: TODO 装饰者(继承结构)
  5. */
  6. public class Decorator extends Drink{
  7. private Drink obj;
  8. public Decorator(Drink obj){ // 组合
  9. this.obj = obj;
  10. }
  11. @Override
  12. public float cost() {
  13. // 操作 装饰者自身 + 具体主体
  14. return super.getPrice() + obj.cost();
  15. }
  16. @Override
  17. public String getDescription() {
  18. // 查看验证
  19. return description + " " + getPrice() + " && " + obj.getDescription();
  20. }
  21. }
  22. // -------------- 以下是具体子装饰类 --------------
  23. class Chocolate extends Decorator{
  24. public Chocolate(Drink obj){
  25. super(obj);
  26. setDescription(" Chocolate ");
  27. setPrice(3.0F);
  28. }
  29. }
  30. class Milk extends Decorator{
  31. public Milk(Drink obj){
  32. super(obj);
  33. setDescription(" Milk ");
  34. setPrice(4.0F);
  35. }
  36. }
  37. class Soy extends Decorator{
  38. Soy(Drink obj){
  39. super(obj);
  40. setDescription(" Soy ");
  41. setPrice(5.0F);
  42. }
  43. }
  • // 测试类 ```java

public class CoffeeBar {

  1. public static void main(String[] args) {
  2. // TODO Auto-generated method stub
  3. // 装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack
  4. // 1. 点一份 LongBlack
  5. Drink order = new LongBlack();
  6. System.out.println("费用1=" + order.cost());
  7. System.out.println("描述=" + order.getDescription());
  8. // 2. order 加入一份牛奶
  9. order = new Milk(order);
  10. System.out.println("order 加入一份牛奶 费用 =" + order.cost());
  11. System.out.println("order 加入一份牛奶 描述 = " + order.getDescription());
  12. // 3. order 加入一份巧克力
  13. order = new Chocolate(order);
  14. System.out.println("order 加入一份牛奶 加入一份巧克力 费用 = " + order.cost());
  15. System.out.println("order 加入一份牛奶 加入一份巧克力 描述 = " + order.getDescription());
  16. // 3. order 加入一份巧克力
  17. order = new Chocolate(order);
  18. System.out.println("order 加入一份牛奶 加入2份巧克力 费用 =" + order.cost());
  19. System.out.println("order 加入一份牛奶 加入2份巧克力 描述 = " + order.getDescription());
  20. System.out.println("===========================");

/* Drink order2 = new DeCaf();

  1. System.out.println("order2 无因咖啡 费用 =" + order2.cost());
  2. System.out.println("order2 无因咖啡 描述 = " + order2.getDescription());
  3. order2 = new Milk(order2);
  4. System.out.println("order2 无因咖啡 加入一份牛奶 费用 =" + order2.cost());
  5. System.out.println("order2 无因咖啡 加入一份牛奶 描述 = " + order2.getDescription());*/
  6. }

}

  1. - // 打印

费用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

  1. <a name="wXvRB"></a>
  2. # 5.经典使用
  3. ---
  4. <a name="b5SbS"></a>
  5. ## 5.1JDK中IO流
  6. <a name="uoO3g"></a>
  7. #### 5.1.1两种设计方案的对比
  8. - 使用继承的硬编码(对继承的不良使用)
  9. - 类数量: 1+n+n*m!/2
  10. - ![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)
  11. - 使用装饰者模式
  12. - 类数量: 1+n+1+m
  13. - ![捕获.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)
  14. <a name="O0Vt7"></a>
  15. #### 5.1.2结构
  16. ![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)
  17. <a name="A3nUu"></a>
  18. #### 5.1.3伪码
  19. ```cpp
  20. //扩展的操作: 在基础流上的加密操作
  21. class CryptoStream: public Stream {
  22. Stream* stream;//...
  23. public:
  24. CryptoStream(Stream* stm):stream(stm){
  25. }
  26. virtual char Read(int number){
  27. //额外的加密操作...
  28. stream->Read(number);//读文件流
  29. }
  30. virtual void Seek(int position){
  31. //额外的加密操作...
  32. stream::Seek(position);//定位文件流
  33. //额外的加密操作...
  34. }
  35. virtual void Write(byte data){
  36. //额外的加密操作...
  37. stream::Write(data);//写文件流
  38. //额外的加密操作...
  39. }
  40. };

5.1.4分析及总结

  1. 如上的C++扩展的加密流的伪码分析:
  2. 1.继承自Stream, 以之加密的操作能配合Stream抽象基类的读写操作(后续由具体的网络流/缓冲流实现)
  3. 2.参数化Stream, 接收对应的实际需要加密的流类型(如上具体的网络流/缓冲流), 以抽象化加密接口.
  4. 否则: 对应的网络流/缓冲流, 都需要一个具体的 "加密网络流"/"加密缓冲流", 导致类爆炸
  5. 3.由编译时装配, 改善成运行时 (组合的方式)装配
  6. 4.(实例教程中)由于所有的扩展类都需要一个Stream成员变量, 所以可以将这个通有Stream抽象到上层.
  7. 而若抽象到基类Stream中, 基类本身并不需要Stream变量, 不符合单一职责.
  8. 故可新建一个二者之间的缓冲层来存放这个子类通用的Stream变量.
  9. 5.Java中:
  10. InputStream基类的直接实现子类FilterInputStream, 即如上抽象层作用.
  11. 其包含一个成员变量{ protected volatile InputStream in; }
  12. JDK FilterInputStream 描述:
  13. FilterInputStream包含一些其他输入流,它用作其基本的数据源,可能会沿途转换数据或提供附加功能。
  14. FilterInputStream本身简单地覆盖了所有InputStream的方法, InputStream版本将所有请求传递给包含的输入流。
  15. FilterInputStream可以进一步覆盖这些方法中的一些,并且还可以提供附加的方法和领域。
  16. 总结:
  17. FilterInputStream 即是父类装饰者, 其具体子类(BufferedInputStream等)即具体装饰者, 扩展了缓冲/BASE编码等功能.
  18. FileInputStream 即父类组件, 其具体子类即具体组件.
  19. (虽然装饰者基类直接装饰的变量类型是InputStream, 但通常都操作 FileInputStream )
  20. (对于OutputStream/Reader/Write同理. Reader基类的直接抽象子类FilterReader, 也包含了一个 Reader 类型的成员变量)
  1. 案例对比IO源码分析:
  2. //1. InputStream是抽象类,类似我们前面讲的Drink
  3. //2. FileInputStream是InputStream 子类,类似我们前面的DeCaf, LongBlack
  4. //3. FilterInputStream是InputStream 子类:类似我们前面的Decorator 修饰者
  5. //4. DataInputStream是FilterInputStream 子类,具体的修饰者,类似前面的Milk, Soy等
  6. //5. FilterInputStream类有protected volatile InputStream in;即含被装饰者
  7. //6.分析得出在 JDK-IO 体系中,就是使用装饰者模式

x.其它

笔记

IO@804L6W1V6MM%$~CZF`BO.jpg