[设计模式] —— Decorator 装饰模式

装饰者模式

装饰模式属于单一职责模式分类里的。软件组件的设计中,如果责任划分的不清晰,使继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码。

动机

过度地使用继承来扩展兑现的功能,由于继承为类型引入的静态特质(定死一定只会调用子类),使扩展的当时缺乏灵活性,并且随着子类的增多(功能扩展的增多),各种子类的组合(对扩展功能的组合)会导致更多子类的膨胀。

如何动态实现“对象功能的扩展”,同时避免组合过多呢?

定义

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

采用组合而非继承的方式,Decorator 模式实现了在运行时动态扩展对象功能的能力。

示例代码
class Stream {
public:
 virtual char Read(int number) = 0;
 virtual void Write(char data) = 0;

 virtual ~Stream() {}
};

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

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

假如现在有一个 Stream 基类,支持流的读和写操作,然后对应派生出了对不同流的支持,这里以文件流类 FileStream 为例和网络流类 NetworkStream 类为例。这时我们的需求发生变化,开始想要支持对现在的流内容提供加密操作,可能第一反应就是继续继承下去示例代码如下:

// 新增支持加密操作子类
class CryptoFileStream : public FileStream {
public:
 virtual char Read(int number) {
    // 额外的加密操作
    FileStream::Read(number);// 读取文件流
 }
 virtual void Write(char data) {
    // 额外的加密操作
    FileStream::Write(data);// 写文件流
    // 额外的加密操作
 }
};

class CryptoNetworkStream : public NetworkStream {
public:
 virtual char Read(int number) {
    // 额外的加密操作
    NetworkStream::Read(number);// 读取网络流
 }
 virtual void Write(char data) {
    // 额外的加密操作
    NetworkStream::Write(data);// 写网络流
    // 额外的加密操作
 }
};

之后可能还会有支持缓存的操作,使用继承就会新增下面的子类:

// 新增支持缓存操作子类
class BufferFileStream : public FileStream {
public:
 virtual char Read(int number) {
    // 额外的缓存操作
    FileStream::Read(number);// 读取文件流
 }
 virtual void Write(char data) {
    // 额外的缓存操作
    FileStream::Write(data);// 写文件流
    // 额外的缓存操作
 }
};

class BufferNetworkStream : public NetworkStream {
public:
 virtual char Read(int number) {
    // 额外的缓存操作
    NetworkStream::Read(number);// 读取网络流
 }
 virtual void Write(char data) {
    // 额外的缓存操作
    NetworkStream::Write(data);// 写网络流
    // 额外的缓存操作
 }
};

当新增一个需要同支持加密缓存的功能,就又要新增形如下面的子类:

// 新增支持加密缓存操作子类
class CryptoBufferFileStream : public FileStream {
public:
 virtual char Read(int number) {
    // 额外的加密操作
    // 额外的缓存操作
    FileStream::Read(number);// 读取文件流
 }
 virtual void Write(char data) {
    // 额外的加密操作
    // 额外的缓存操作
    FileStream::Write(data);// 写文件流
    // 额外的加密操作
    // 额外的缓存操作
 }
};

class CryptoBufferNetworkStream : public NetworkStream {
public:
 virtual char Read(int number) {
    // 额外的加密操作
    // 额外的缓存操作
    NetworkStream::Read(number);// 读取网络流
 }
 virtual void Write(char data) {
    // 额外的加密操作
    // 额外的缓存操作
    NetworkStream::Write(data);// 写网络流
    // 额外的加密操作
    // 额外的缓存操作
 }
};

这里只是以两个子类来做说明,还可以有更多地 Stream 子类,如果每一次要增加新的功能都是使用继承,每一个子类会衍生出更多地子类,功能之间可能存在上面的组合关系,每一个子类都要继承一遍,并且这些子类的功能之间主题都是相似的,只是部分细节不同。然后在使用时可能是拿着现有的子类静态生成对象:

void Process(){
    //编译时装配
    CryptoFileStream *fs1 = new CryptoFileStream();
    BufferedFileStream *fs2 = new BufferedFileStream();
    CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream();
}

上面的代码可以看到 // 额外的加密操作 以及 // 额外的缓存操作 表示的代码,在各个子类里出现大量的重复,设计模式里有一个原则是组合优于继承,这里当使用组合的形式来改写上述代码,可以有如下效果:

// 新增支持加密操作子类,继承改组合
class CryptoFileStream {
  FileStream* stream;
public:
 virtual char Read(int number) {
    // 额外的加密操作
    stream->Read(number);// 读取文件流
 }
 virtual void Write(char data) {
    // 额外的加密操作
    stream->Write(data);// 写文件流
    // 额外的加密操作
 }
};

class CryptoNetworkStream {
   NetworkStream* stream;
public:
 virtual char Read(int number) {
    // 额外的加密操作
    stream->Read(number);// 读取网络流
 }
 virtual void Write(char data) {
    // 额外的加密操作
    stream->Write(data);// 写网络流
    // 额外的加密操作
 }
};

可以看到,上面两个类的内容,继承转组合以后唯一的区别就是成员变量 stream 类型不同,但是它们又都是 Stream 的子类,更进一步重构手法,就是直接将成员变量 stream 设成父类,使用多态。

class CryptoStream {
  Stream* stream;
public:
 CryptoStream(Stream* s) : stream(s){}
 virtual char Read(int number) {
    // 额外的加密操作
    stream->Read(number);// 读取文件流
 }
 virtual void Write(char data) {
    // 额外的加密操作
    stream->Write(data);// 写文件流
    // 额外的加密操作
 }
};

这里使用多态 + 组合的方式就可以消除子类,并且消除重复代码了。扩展到其他两个新增子类也是一样的手法,然后现在重复性问题得到缓解之后,为了得到虚函数的接口规范,这里再继承一下 Stream 基类:

// 继承 Stream 是为了获得虚函数接口规范、
// 组合 Stream 是为了多态调用其中的成员函数
class CryptoStream : public Stream {
  Stream* stream;
public:
 CryptoStream(Stream* s) : stream(s){}
 virtual char Read(int number) {
    // 额外的加密操作
    stream->Read(number);// 读取xx流
 }
 virtual void Write(char data) {
    // 额外的加密操作
    stream->Write(data);// 写xx流
    // 额外的加密操作
 }
};

上面的这个改动其实就是Decorator 装饰模式。最后给出相对完整的示例代码:

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

 virtual ~Stream() {}
};

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

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

// 使用装饰模式之后的代码
class CryptoStream : public Stream {
  Stream* stream;
public:
 CryptoStream(Stream* s) : stream(s){}
 virtual char Read(int number) {
    // 额外的加密操作
    stream->Read(number);// 读取xx流
 }
 virtual void Write(char data) {
    // 额外的加密操作
    stream->Write(data);// 写xx流
    // 额外的加密操作
 }
};

class BufferStream : public Stream {
  Stream* stream;
public:
 BufferStream(Stream* s) : stream(s){}
 virtual char Read(int number) {
    // 额外的缓存操作
    stream->Read(number);// 读取xx流
 }
 virtual void Write(char data) {
    // 额外的缓存操作
    stream->Write(data);// 写xx流
    // 额外的缓存操作
 }
};

并且在使用的时候会是如下使用方式:

void Process(){
    //运行时装配
    FileStream* s1=new FileStream(); // 可以单独使用
    CryptoStream* s2=new CryptoStream(s1); // 在 s1 的基础上加密
    BufferStream* s3=new BufferStream(s1); // 在 s1 的基础上缓存
    
    BufferStream* s4=new BufferStream(s2); // 在加密操作 s2 的基础上缓存
}

可以看到这里 s4 是在加密操作 s2 基础上实现了加密缓存的操作,这样也代替了前面的 CryptoBufferStream 子类,即在传入类的基础上增加功能,这就是装饰模式的名字由来。

结构图
总结

采用组合而非继承的手法,Decorator 模式实现了在运行时动态扩展对象功能的能力。鉴于其实现模式即继承接口基类,又使用基类实现多态,所以在接口表现上 Decorator 模式是 is a 的关系,在实现上又是 has a 的关系。支持主体类在多方向上的扩展是为“装饰”的含义。

其他设计模式汇总:
[设计模式] —— 设计模式的介绍及分类

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值