C++ 设计模式 装饰模式

以下内容均来自GeekBand极客班C++ 设计模式课程(李建忠老师主讲)

参考文献见:https://download.csdn.net/download/qq_41605114/18881763

Decorator

“单一职责”模式:

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

典型模式:

Decorator

Bridge

动机(Motivation)

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

如何使“对象功能的扩展”能够根据需要来动态地实现?同时避免“扩展功能的增多”带来的子类膨胀问题?从而使得任何“功能扩展变化”所导致的影响降到最低。

《设计模式》GOF的定义:

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

蓝色为稳定部分,橘黄色为变化部分

下面我们以一个流操作为案例:

结构化编程

流的主体如下:

class Stream{
public:
    virtual char Read(int number) = 0;
    virtual void Seek(int position) = 0;
    virtual void Write(char data) = 0;

    virtual ~Stream(){}
};

具体的实现内容如下:

//主体类
class FileStream : public Stream{
public:
    virtual char Read(int number){
        //...读文件流
    }
    virtual void Seek(int position){
        //...定位文件流
    }
    virtual void Write(char data){
        //...写文件流
    }
};

class NetworkStream : public Stream{
public:
    virtual char Read(int number){
        //读网络流
    }
    virtual void Seek(int position){
        //...定位网络流
    }
    virtual void Write(char data){
        //...写网络流
    }
};

class MemoryStream : public Stream{
public:
    virtual char Read(int number){
        //读内存流
    }
    virtual void Seek(int position){
        //...定位内存流
    }
    virtual void Write(char data){
        //...写内存流
    }
};

那么现在需要在具体实现中增加一些辅助功能,比如加密或者缓存

class CrytoFileStream : public FileStream{
public:
    virtual char Read(int number){
        //额外的加密操作
        FileStream::Read(number);//直接用父类的,此处必须声明作用域
        //...读文件流
    }
    virtual void Seek(int position){
        //额外的加密操作
        FileStream::Seek(position);//直接用父类的,此处必须声明作用域
        //...定位文件流
    }
    virtual void Write(char data){
        //额外的加密操作
        FileStream::Write(data);//直接用父类的,此处必须声明作用域
        //...写文件流
    }
};

class CrytoNetworkStream : public NetworkStream{

};

class CrytoMemoryStream : public MemoryStream{

};

增加缓冲区:

//增加缓冲区
class BufferedFileStream : public FileStream{

};

class BufferedNetworkStream : public NetworkStream{

};

class BufferedMemoryStream : public MemoryStream{

};

现在对上述的继承关系进行梳理,树状图如下: 

从上图可知,如果继续增加需求,那么会导致子类不断膨胀,此时显然是要进行优化,划分清楚责任

为了优化整个结构,我们首先对继承部分进行优化:

利用组合替代继承:

class CrytoFileStream{
     FileStream * stream;
public:
    virtual char Read(int number){
        //额外的加密操作
        stream->Read(number);//直接用父类的,此处必须声明作用域
        //...读文件流
    }
    virtual void Seek(int position){
        //额外的加密操作
        stream->Seek(position);//直接用父类的,此处必须声明作用域
        //...定位文件流
    }
    virtual void Write(char data){
        //额外的加密操作
        stream->Write(data);//直接用父类的,此处必须声明作用域
        //...写文件流
    }
};

对比一下代码段:

继承 :                                                                                                              组合:

我们可以看到,使用继承的CrytoFileStream class中的FileStream::Read/Seek/Write部分,全部是静态特质,是无法进行扩展的,只能进行改变。

直接替换成使用组合的CrytoFileStream class

下面我们对所有的扩展内容都进行同样的操作:

class CrytoFileStream{
     FileStream * stream;
public:
    virtual char Read(int number){
        //额外的加密操作
        stream->Read(number);//直接用父类的,此处必须声明作用域
        //...读文件流
    }
};
class CrytoNetworkStream{
    NetworkStream * stream;
public:
   virtual char Read(int number){
       //额外的加密操作
       stream->Read(number);//直接用父类的,此处必须声明作用域
       //...读文件流
   }
};
class CrytoMemoryStream{
    MemoryStream * stream;
public:
   virtual char Read(int number){
       //额外的加密操作
       stream->Read(number);//直接用父类的,此处必须声明作用域
       //...读文件流
   }
};

可以看到,上面的代码更加接近了,加密扩展到的三个类几乎是在完成相同的操作

我们直接把FileStream,NetworkStream,MemoryStream的对象Stream完全可以使用多态进行代替

直接

将上面三个类合为一个类

class CrytoStream : public Stream{
     Stream * stream;
public:
    virtual char Read(int number){
        //额外的加密操作
        stream->Read(number);//直接用父类的,此处必须声明作用域
        //...读文件流
    }
    virtual void Seek(int position){
        //额外的加密操作
        stream->Seek(position);//直接用父类的,此处必须声明作用域
        //...定位文件流
    }
    virtual void Write(char data){
        //额外的加密操作
        stream->Write(data);//直接用父类的,此处必须声明作用域
        //...写文件流
    }
};

 同理,Buffer类也可以合为一个类

//增加缓冲区
class BufferedStream : public Stream{
    Stream * stream;
public:
    BufferedStream(Stream * arg):stream(arg){

    }
   virtual char Read(int number){
       //额外的缓冲操作
       stream->Read(number);//直接用父类的,此处必须声明作用域
       //...读文件流
   }
   virtual void Seek(int position){
       //额外的缓冲操作
       stream->Seek(position);//直接用父类的,此处必须声明作用域
       //...定位文件流
   }
   virtual void Write(char data){
       //额外的缓冲操作
       stream->Write(data);//直接用父类的,此处必须声明作用域
       //...写文件流
   }
};

从CrytoStream类和bufferedStream类中可以看出,其中含有相同的字段,那么按照重构的理论,要么把这些字段提取出来放在父类中,要么就做一个中间类

放在父类中的话,FileStream不需要这个字段,所以没有任何的必要

那么现在做一个中间类 DecoratorStream类,具体操作如下:

class DecoratorStream : public Stream{
protected:
    Stream * stream;
public:
    DecoratorStream(Stream * arg):stream(arg){
        
    }
};

class CrytoStream : public DecoratorStream{   
public:
     CrytoStream(Stream * arg):DecoratorStream(arg){

     }
    virtual char Read(int number){
        //额外的加密操作
        stream->Read(number);//直接用父类的,此处必须声明作用域
        //...读文件流
    }
    virtual void Seek(int position){
        //额外的加密操作
        stream->Seek(position);//直接用父类的,此处必须声明作用域
        //...定位文件流
    }
    virtual void Write(char data){
        //额外的加密操作
        stream->Write(data);//直接用父类的,此处必须声明作用域
        //...写文件流
    }
};

//增加缓冲区
class BufferedStream : public DecoratorStream{
public:
    BufferedStream(Stream * arg):DecoratorStream(arg){

    }
   virtual char Read(int number){
       //额外的缓冲操作
       stream->Read(number);//直接用父类的,此处必须声明作用域
       //...读文件流
   }
   virtual void Seek(int position){
       //额外的缓冲操作
       stream->Seek(position);//直接用父类的,此处必须声明作用域
       //...定位文件流
   }
   virtual void Write(char data){
       //额外的缓冲操作
       stream->Write(data);//直接用父类的,此处必须声明作用域
       //...写文件流
   }
};

加上最开始的抽象类和主体类

//Decorator
class Stream{
public:
    virtual char Read(int number) = 0;
    virtual char Seek(int position) = 0;
    virtual char Write(char data) = 0;

    virtual ~Stream(){}
};
//主体类
class FileStream : public Stream{
public:
    virtual char Read(int number){
        //...读文件流
    }
    virtual char Seek(int position){
        //...定位文件流
    }
    virtual char Write(char data){
        //...写文件流
    }
};
class NetworkStream : public Stream{
public:
    virtual char Read(int number){
        //读网络流
    }
    virtual void Seek(int position){
        //...定位网络流
    }
    virtual void Write(char data){
        //...写网络流
    }
};
class MemoryStream : public Stream{
public:
    virtual char Read(int number){
        //读内存流
    }
    virtual void Seek(int position){
        //...定位内存流
    }
    virtual void Write(char data){
        //...写内存流
    }
};

下面我们可以看出,树状结构转化为了下面的形式

要点总结

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

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

3,DecoratorStream模式的目的并非解决“多子类衍生的多继承”问题,DecoratorStream模式应用的要点在于解决“主体类在多个方向上的扩展功能”——是为“装饰”的含义。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值