• c++ 设计模式6 (Decorator 装饰模式)


    4. “单一职责”类模式

    在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任。

    典型模式代表: Decorator,Bridge

    4.1 Decorator 装饰模式

    代码示例:不同的流操作(文件流,网络流,内存流)及其扩展功能(加密,缓冲)等的实现

    实现代码1:

    类图结构示意(大量使用继承)

    数据规模: 假设有n种文件,m种功能操作。该实现方法有(1 + n + n * m! / 2) 数量级的子类;

    同时考察59行,79行,98行本身是相同的代码(类似还有很多),存在大量的冗余和重复。

    开始重构,见方法2.

    复制代码
      1 //Decorator1.cpp
      2 //业务操作
      3 class Stream{
      4 public:
      5     virtual char Read(int number)=0;
      6     virtual void Seek(int position)=0;
      7     virtual void Write(char data)=0;
      8     
      9     virtual ~Stream(){}
     10 };
     11 
     12 //主体类
     13 class FileStream: public Stream{
     14 public:
     15     virtual char Read(int number){
     16         //读文件流
     17     }
     18     virtual void Seek(int position){
     19         //定位文件流
     20     }
     21     virtual void Write(char data){
     22         //写文件流
     23     }
     24 
     25 };
     26 
     27 class NetworkStream :public Stream{
     28 public:
     29     virtual char Read(int number){
     30         //读网络流
     31     }
     32     virtual void Seek(int position){
     33         //定位网络流
     34     }
     35     virtual void Write(char data){
     36         //写网络流
     37     }
     38     
     39 };
     40 
     41 class MemoryStream :public Stream{
     42 public:
     43     virtual char Read(int number){
     44         //读内存流
     45     }
     46     virtual void Seek(int position){
     47         //定位内存流
     48     }
     49     virtual void Write(char data){
     50         //写内存流
     51     }
     52     
     53 };
     54 
     55 //扩展操作
     56 class CryptoFileStream :public FileStream{
     57 public:
     58     virtual char Read(int number){
     59        
     60         //额外的加密操作...
     61         FileStream::Read(number);//读文件流
     62         
     63     }
     64     virtual void Seek(int position){
     65         //额外的加密操作...
     66         FileStream::Seek(position);//定位文件流
     67         //额外的加密操作...
     68     }
     69     virtual void Write(byte data){
     70         //额外的加密操作...
     71         FileStream::Write(data);//写文件流
     72         //额外的加密操作...
     73     }
     74 };
     75 
     76 class CryptoNetworkStream : :public NetworkStream{
     77 public:
     78     virtual char Read(int number){
     79         
     80         //额外的加密操作...
     81         NetworkStream::Read(number);//读网络流
     82     }
     83     virtual void Seek(int position){
     84         //额外的加密操作...
     85         NetworkStream::Seek(position);//定位网络流
     86         //额外的加密操作...
     87     }
     88     virtual void Write(byte data){
     89         //额外的加密操作...
     90         NetworkStream::Write(data);//写网络流
     91         //额外的加密操作...
     92     }
     93 };
     94 
     95 class CryptoMemoryStream : public MemoryStream{
     96 public:
     97     virtual char Read(int number){
     98         
     99         //额外的加密操作...
    100         MemoryStream::Read(number);//读内存流
    101     }
    102     virtual void Seek(int position){
    103         //额外的加密操作...
    104         MemoryStream::Seek(position);//定位内存流
    105         //额外的加密操作...
    106     }
    107     virtual void Write(byte data){
    108         //额外的加密操作...
    109         MemoryStream::Write(data);//写内存流
    110         //额外的加密操作...
    111     }
    112 };
    113 
    114 class BufferedFileStream : public FileStream{
    115     //...
    116 };
    117 
    118 class BufferedNetworkStream : public NetworkStream{
    119     //...
    120 };
    121 
    122 class BufferedMemoryStream : public MemoryStream{
    123     //...
    124 }
    125 
    126 
    127 
    128 
    129 class CryptoBufferedFileStream :public FileStream{
    130 public:
    131     virtual char Read(int number){
    132         
    133         //额外的加密操作...
    134         //额外的缓冲操作...
    135         FileStream::Read(number);//读文件流
    136     }
    137     virtual void Seek(int position){
    138         //额外的加密操作...
    139         //额外的缓冲操作...
    140         FileStream::Seek(position);//定位文件流
    141         //额外的加密操作...
    142         //额外的缓冲操作...
    143     }
    144     virtual void Write(byte data){
    145         //额外的加密操作...
    146         //额外的缓冲操作...
    147         FileStream::Write(data);//写文件流
    148         //额外的加密操作...
    149         //额外的缓冲操作...
    150     }
    151 };
    152 
    153 
    154 
    155 void Process(){
    156 
    157         //编译时装配
    158     CryptoFileStream *fs1 = new CryptoFileStream();
    159 
    160     BufferedFileStream *fs2 = new BufferedFileStream();
    161 
    162     CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream();
    163 
    164 }
    复制代码

    实现代码2:

    针对上述代码,重构步骤如下:

    1)考察 CryptoFileStream ,CryptoNetworkStream,CryptoMemoryStream三个类,将其继承FileStream,NetworkStream,NetworkStream改为组合;即

    复制代码
     1 class CryptoFileStream{
     2     FileStream* stream;
     3 public:
     4     virtual char Read(int number){
     5        
     6         //额外的加密操作...
     7         stream -> Read(number);//改用字段方式调用Read()
     8         // ...seek() write() 同理
     9     }
    10 }    
    11 
    12 class CryptoNetworkStream{
    13     NetworkStream* stream;
    14 public:
    15     virtual char Read(int number){
    16         
    17         //额外的加密操作...
    18         stream -> Read(number);//改用字段方式调用Read()
    19         //... seek() write() 同理
    20     }
    21 }    
    22 
    23 class CryptoMemoryStream{
    24     MemoryStream* stream;
    25 public:
    26     virtual char Read(int number){
    27         
    28         //额外的加密操作...
    29         stream -> Read(number);//改用字段方式调用Read()
    30         //... seek() write() 同理
    31     }
    32 }    
    复制代码

    2)考察上述2行, 13行, 24行, 发现其均为Stream子类, 应使用多态性继续重构。

    复制代码
     1 class CryptoFileStream{
     2     Stream* stream; // = new FileStream()
     3 public:
     4     virtual char Read(int number){
     5        
     6         //额外的加密操作...
     7         stream -> Read(number);//改用字段方式调用Read()
     8         // ...seek() write() 同理
     9     }
    10 }    
    11 
    12 class CryptoNetworkStream{
    13     Stream* stream; // = new NetworkStream();
    14 public:
    15     virtual char Read(int number){
    16         
    17         //额外的加密操作...
    18         stream -> Read(number);//改用字段方式调用Read()
    19         //... seek() write() 同理
    20     }
    21 }    
    22 
    23 class CryptoMemoryStream{
    24     Stream* stream; // = newMemoryStream()
    25 public:
    26     virtual char Read(int number){
    27         
    28         //额外的加密操作...
    29         stream -> Read(number);//改用字段方式调用Read()
    30         //... seek() write() 同理
    31     }
    32 }    
    复制代码

    3)发现三个类是相同的,不同的实现(需求的变化)是在运行时实现,编译时复用,改为一个类即可,命名为CryptoStream。

    同时为了保证接口规范(read,seek等仍然是虚函数),继承Stream,出现既有组合,又有继承的情况。

    复制代码
     1 class CryptoStream : public Stream{
     2     Stream* stream; // = new ...
     3 public:
     4     virtual char Read(int number){
     5        
     6         //额外的加密操作...
     7         stream -> Read(number);//改用字段方式调用Read()
     8         // ...seek() write() 同理
     9     }
    10 }   
    复制代码

    4)添加相应构造器,得到此轮重构后的结果,代码如下,主要查看使用方式(运行时装配):

    复制代码
      1 //Decorator2.cpp
      2 class Stream{
      3 
      4 public:
      5     virtual char Read(int number)=0;
      6     virtual void Seek(int position)=0;
      7     virtual void Write(char data)=0;
      8     
      9     virtual ~Stream(){}
     10 };
     11 
     12 //主体类
     13 class FileStream: public Stream{
     14 public:
     15     virtual char Read(int number){
     16         //读文件流
     17     }
     18     virtual void Seek(int position){
     19         //定位文件流
     20     }
     21     virtual void Write(char data){
     22         //写文件流
     23     }
     24 
     25 };
     26 
     27 class NetworkStream :public Stream{
     28 public:
     29     virtual char Read(int number){
     30         //读网络流
     31     }
     32     virtual void Seek(int position){
     33         //定位网络流
     34     }
     35     virtual void Write(char data){
     36         //写网络流
     37     }
     38     
     39 };
     40 
     41 class MemoryStream :public Stream{
     42 public:
     43     virtual char Read(int number){
     44         //读内存流
     45     }
     46     virtual void Seek(int position){
     47         //定位内存流
     48     }
     49     virtual void Write(char data){
     50         //写内存流
     51     }
     52     
     53 };
     54 
     55 //扩展操作
     56 
     57 
     58 class CryptoStream: public Stream {
     59     
     60     Stream* stream;//...
     61 
     62 public:
     63     CryptoStream(Stream* stm):stream(stm){
     64     
     65     }
     66     
     67     
     68     virtual char Read(int number){
     69        
     70         //额外的加密操作...
     71         stream->Read(number);//读文件流
     72     }
     73     virtual void Seek(int position){
     74         //额外的加密操作...
     75         stream::Seek(position);//定位文件流
     76         //额外的加密操作...
     77     }
     78     virtual void Write(byte data){
     79         //额外的加密操作...
     80         stream::Write(data);//写文件流
     81         //额外的加密操作...
     82     }
     83 };
     84 
     85 
     86 
     87 class BufferedStream : public Stream{
     88     
     89     Stream* stream;//...
     90     
     91 public:
     92     BufferedStream(Stream* stm):stream(stm){
     93         
     94     }
     95     //...
     96 };
     97 
     98 
     99 
    100 
    101 
    102 void Process(){
    103 
    104     //运行时装配
    105     FileStream* s1=new FileStream();
    106     CryptoStream* s2=new CryptoStream(s1);
    107     
    108     BufferedStream* s3=new BufferedStream(s1);
    109     
    110     BufferedStream* s4=new BufferedStream(s2);
    111     
    112     
    113 
    114 }
    复制代码

    实现代码3:

    上述实现代码2已经极大地缓解了冗余问题,符合面向对象的设计思想,该轮重构是锦上添花。

    重构步骤如下:

    考察上述代码,多个子类都有同样的字段(Stream* stream;//...)

    应考虑“往上提”,方法有两种,第一种是提到基类(显然不合适,FileStream等并不需要Stream字段 )

    所以考虑第二种方法,实现一个“中间类”。

    复制代码
    DecoratorStream: public Stream{
    protected:
        Stream* stream;//...
        
        DecoratorStream(Stream * stm):stream(stm){
        
        }
        
    };
    复制代码

    CryptoStream等继承中间类DecoratorStream:

    复制代码
    class CryptoStream: public DecoratorStream {
     
    public:
        CryptoStream(Stream* stm):DecoratorStream(stm){
        
        }
        //...
    }    
    复制代码

    重构完成的最终版本:

    FileStream,NetworkStream,MemoryStream等可以创建各自的对象;

    但实现加密,缓存功能必须在已有FileStream/NetworkStream等对象基础上;

    这些操作本质是扩展操作,也就是“装饰”的含义。

    此时类图示意:

    这时类的数量为(1 + n + 1 + m)

    复制代码
      1 //Decorator3.cpp
      2 class Stream{
      3 
      4 public:
      5     virtual char Read(int number)=0;
      6     virtual void Seek(int position)=0;
      7     virtual void Write(char data)=0;
      8     
      9     virtual ~Stream(){}
     10 };
     11 
     12 //主体类
     13 class FileStream: public Stream{
     14 public:
     15     virtual char Read(int number){
     16         //读文件流
     17     }
     18     virtual void Seek(int position){
     19         //定位文件流
     20     }
     21     virtual void Write(char data){
     22         //写文件流
     23     }
     24 
     25 };
     26 
     27 class NetworkStream :public Stream{
     28 public:
     29     virtual char Read(int number){
     30         //读网络流
     31     }
     32     virtual void Seek(int position){
     33         //定位网络流
     34     }
     35     virtual void Write(char data){
     36         //写网络流
     37     }
     38     
     39 };
     40 
     41 class MemoryStream :public Stream{
     42 public:
     43     virtual char Read(int number){
     44         //读内存流
     45     }
     46     virtual void Seek(int position){
     47         //定位内存流
     48     }
     49     virtual void Write(char data){
     50         //写内存流
     51     }
     52     
     53 };
     54 
     55 //扩展操作
     56 
     57 DecoratorStream: public Stream{
     58 protected:
     59     Stream* stream;//...
     60     
     61     DecoratorStream(Stream * stm):stream(stm){
     62     
     63     }
     64     
     65 };
     66 
     67 class CryptoStream: public DecoratorStream {
     68  
     69 
     70 public:
     71     CryptoStream(Stream* stm):DecoratorStream(stm){
     72     
     73     }
     74     
     75     
     76     virtual char Read(int number){
     77        
     78         //额外的加密操作...
     79         stream->Read(number);//读文件流
     80     }
     81     virtual void Seek(int position){
     82         //额外的加密操作...
     83         stream::Seek(position);//定位文件流
     84         //额外的加密操作...
     85     }
     86     virtual void Write(byte data){
     87         //额外的加密操作...
     88         stream::Write(data);//写文件流
     89         //额外的加密操作...
     90     }
     91 };
     92 
     93 
     94 
     95 class BufferedStream : public DecoratorStream{
     96     
     97     Stream* stream;//...
     98     
     99 public:
    100     BufferedStream(Stream* stm):DecoratorStream(stm){
    101         
    102     }
    103     //...
    104 };
    105 
    106 
    107 
    108 
    109 void Process(){
    110 
    111     //运行时装配
    112     FileStream* s1=new FileStream();
    113     
    114     CryptoStream* s2=new CryptoStream(s1);
    115     
    116     BufferedStream* s3=new BufferedStream(s1);
    117     
    118     BufferedStream* s4=new BufferedStream(s2);
    119     
    120     
    121 
    122 }
    复制代码

    Decorator模式使用动机:

    在某些情况下我们可能会“过度地使用继承来扩展对象的功能”,由于基础为类型引入的静态特指,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各个子类的组合(扩展功能的组合)会导致各种子类的膨胀。

    模式定义:

    动态(组合)地给一个对象增加一些额外的指责。就增加功能而言,Decorator模式比声场子类(继承)更为灵活(消除重复代码&减少子类个数)

    类图:

    要点总结:

    1.通过采用组合并非继承的手法,Decorator模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用继承带来的”灵活性差“和”多子类衍生问题“

    2.Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类。

    3.Decorator模式的目的并非解决”多字类衍生的多继承“问题,Decorator模式应用的要点在于解决”主体类在多个方向上的扩展功能“(显然file,network与加密,缓冲是两种扩展方向) ——是为”装饰“的含义。

    参考文献:

    李建忠老师 《C++设计模式》网络课程

    《设计模式:可复用面向对象软件的基础》

  • 相关阅读:
    JavaScript DOMContentLoaded 和 load事件的区别
    Redux源码分析之combineReducers
    Redux源码分析之createStore
    LeetCode003 无重复字符的最长子串
    Navicat Premium 15 永久破解和2021版本最新IDEA破解(亲测有效)
    在Win10中安装虚拟机:VMware Workstation Player+Ubuntu20.04
    C# 对象复制三种方法效率对比——反射、序列化、表达式树
    nginx在个人网站上的优化(一)
    VUE3.0的打包配置修改
    Jenkins构建项目连接Repository URL的填坑之路
  • 原文地址:https://www.cnblogs.com/yechanglv/p/6931016.html
Copyright © 2020-2023  润新知