第6节 装饰者模式(单一职责)

一、单一职责模式概述

  1. 在软件设计中,如果职责划分不清晰,使用继承得到的结果往往随着需求的变化子类急剧膨胀,同时充斥着冗余的代码;
  2. 单一职责模式典型:装饰者模式、桥模式

二、装饰者模式动机

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

三、程序示例

我们打算做一些IO操作,设计一些关于流的库.

3.1 使用设计模式前

#include 

class Stream {                                        // 基类:定义流的一些基本操作
public:
    virtual char Read(int number)   = 0;              // 纯虚函数
    virtual void Seek(int position) = 0;
    virtual void Write(char data)   = 0;
    virtual ~Stream() {};                             // 养成良好的习惯,基类析构函数写成虚函数
};

// 主体类:文件流操作,继承自Stream
class FileStream : public Stream {
public:
    virtual char Read(int number)  {/* code... */ }   // 读取文件流
    virtual void Seek(int position){/* code... */ }   // 定位文件流
    virtual void Write(char data)  {/* code... */ }   // 写入文件流
};

// 主体类:网络流操作,继承自Stream
class NetworkStream :public Stream {
public:
    virtual char Read(int number)   {/* code... */ }   // 读取网络流
    virtual void Seek(int position) {/* code... */ }   // 定位网络流
    virtual void Write(char data)   {/* code... */ }   // 写入网络流
};

// 主体类:内存流操作,继承自Stream
class MemoryStream :public Stream {
public:
    virtual char Read(int number)   {/* code... */ }   // 读取内存流
    virtual void Seek(int position) {/* code... */ }   // 定位内存流
    virtual void Write(char data)   {/* code... */ }   // 写入内存流
};

// 现在考虑扩展操作,如果我们要对文件流进行加密
class CryptoFileStream :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 CryptoNetworkStream :public NetworkStream {
public:
    virtual char Read(int number) {                     // 读取网络流
		//  额外的加密操作
		NetworkStream::Read(number);
    }
    virtual void Seek(int position) {                   // 定位网络流
		//  额外的加密操作
		NetworkStream::Seek(position);
    }
    virtual void Write(char data) {                     // 写入网络流
		//  额外的加密操作
		NetworkStream::Write(data);
    }
};

class CryptoMemoryStream :public MemoryStream {
public:
    virtual char Read(int number) {                     // 读取内存流
		//  额外的加密操作
		MemoryStream::Read(number);
    }
    virtual void Seek(int position) {                   // 定位内存流
		//  额外的加密操作
		MemoryStream::Seek(position);
    }
    virtual void Write(char data) {                     // 写入内存流
		//  额外的加密操作
		MemoryStream::Write(data);
    }
};

// 继续对FileStream进行扩展
class BufferedFileStream:public FileStream{ /* 额外的缓冲操作*/  };

// 对文件既有缓冲扩展又有加密扩展
class CryptoBufferedFileStream :public FileStream {     // 当然也可继承自BufferedFileStream
	// 额外的缓冲操作
	// 额外的加密操作
};

int main()
{
    // 使用设计模式前,发生的是编译时装配
    CryptoFileStream   *fs1 = new CryptoFileStream();
    BufferedFileStream *fs2 = new BufferedFileStream();
    CryptoBufferedFileStream *fs3 = new CryptoBufferedFileStream();

    /*codes to do something... */
    delete fs3;
    delete fs2;
    delete fs1;
    renturn 0;
}

3.2 问题分析

使用设计模式前,代码结构关系如下:

                                            Stream
	                                          ||
           FileStream                    NetworkStream                    MemoryStream
	          ||                              ||                               ||
        CryptoFileStream               CryptoNetworkStream             CryptoMemoryStream
       BufferedFileStream             BufferedNetworkStream           BufferedMemoryStream
    CryptoBufferedFileStream       CryptoBufferedNetworkStream      CryptoBufferedMemoryStream

假设扩展类型数量为N,操作种类为M,则在谁用设计模式前,类的总量为O(MN),当M/N数量较多时,类数量是很大的;另一方面,我们发现类代码里有很多重复冗余的操作.

我们是否能够把O(MN) 变为O(M+N)?在OOP设计原则里,提到,能够使用组合的方式我们尽量不适用继承,使用组合方式后是否能讲类数量变为O(M+N)?

3.3 使用装饰者模式

#include 

class Stream {                                        // 基类:定义流的一些基本操作
public:
    virtual char Read(int number)   = 0;
    virtual void Seek(int position) = 0;
    virtual void Write(char data)   = 0;
    virtual ~Stream() {};                             // 养成良好的习惯,基类析构函数写成虚函数
};

// 主体类:文件流操作,继承自Stream
class FileStream : public Stream {
public:
    virtual char Read(int number)  {/* code... */ }   // 读取文件流
    virtual void Seek(int position){/* code... */ }   // 定位文件流
    virtual void Write(char data)  {/* code... */ }   // 写入文件流
};

// 主体类:网络流操作,继承自Stream
class NetworkStream :public Stream {
public:
    virtual char Read(int number)   {/* code... */ }   // 读取网络流
    virtual void Seek(int position) {/* code... */ }   // 定位网络流
    virtual void Write(char data)   {/* code... */ }   // 写入网络流
};

// 主体类:内存流操作,继承自Stream
class MemoryStream :public Stream {
public:
    virtual char Read(int number)   {/* code... */ }   // 读取内存流
    virtual void Seek(int position) {/* code... */ }   // 定位内存流
    virtual void Write(char data)   {/* code... */ }   // 写入内存流
};

class CryptoStream{
public:
    CryptoStream(Stream* stream) { this->m_stream = stream; }
public:
    virtual char Read(int number) {                     
		doCrypto();
		m_stream->Read(number);
    }
    virtual void Seek(int position) {                  
		doCrypto();
		m_stream->Seek(position);
    }
    virtual void Write(char data) {                     
		doCrypto();
		m_stream->Write(data);
    }
private:
    void doCrypto() {}
private:
    Stream* m_stream;                    // 编译时无关,运行时根据new的具体对象决定调用什么方法
};

class BufferedStream{
public:
    BufferedStream(Stream* stream){ this->m_stream = stream; }      
public:
    virtual char Read(int number) {                    
		doBuffered();
		m_stream->Read(number);
    }
    virtual void Seek(int position) {                  
		doBuffered();
		m_stream->Seek(position);
    }
    virtual void Write(char data) {                    
		doBuffered();
		m_stream->Write(data);
    }
private:
    void doBuffered() {}
private:
    Stream* m_stream;                                  // 编译时无关,运行时根据new的具体对象决定调用什么方法
};    

int main()
{
    // 使用设计模式后,类的数量变为加法关系,而不再是乘法关系(CryptoStream,BufferedStream)
    Stream         *s1 = new FileStream();
    CryptoStream   *s2 = new CryptoStream(s1);
    BufferedStream *s3 = new BufferedStream(s1);

    /*codes to do something... */
    delete s1; delete s2; delete s3;
    renturn 0;
}

3.4 总结分析

使用设计模式后,类的数量变为加法关系,而非乘法关系(CryptoStream,BufferedStream).

四、再思考

如何实现既加密、又缓存?

  • 上述代码s2和s3是相互分开的,相互直连没有联系,怎么实现交互? 他们之间的共同点是?自然是基类了
  • 因此我们不仅需要在类中声明Stream对象,还需要继承该类,而且需要实现基类的虚函数,才能new出一个对象,不实现基类虚方法是不能构造对象的.
  • 此时,应用代码可以写成:
#include 

class Stream {                                        // 基类:定义流的一些基本操作
public:
    virtual char Read(int number)   = 0;
    virtual void Seek(int position) = 0;
    virtual void Write(char data)   = 0;
    virtual ~Stream() {};                             // 养成良好的习惯,基类析构函数写成虚函数
};

// 主体类:文件流操作,继承自Stream
class FileStream : public Stream {
public:
    virtual char Read(int number)  {/* code... */ }   // 读取文件流
    virtual void Seek(int position){/* code... */ }   // 定位文件流
    virtual void Write(char data)  {/* code... */ }   // 写入文件流
};

// 主体类:网络流操作,继承自Stream
class NetworkStream :public Stream {
public:
    virtual char Read(int number)   {/* code... */ }   // 读取网络流
    virtual void Seek(int position) {/* code... */ }   // 定位网络流
    virtual void Write(char data)   {/* code... */ }   // 写入网络流
};

// 主体类:内存流操作,继承自Stream
class MemoryStream :public Stream {
public:
    virtual char Read(int number)   {/* code... */ }   // 读取内存流
    virtual void Seek(int position) {/* code... */ }   // 定位内存流
    virtual void Write(char data)   {/* code... */ }   // 写入内存流
};

class CryptoStream:public Stream{
public:
    CryptoStream(Stream* stream) { this->m_stream = stream; }
public:
    virtual char Read(int number) {                     
		doCrypto();
		m_stream->Read(number);
    }
    virtual void Seek(int position) {                  
		doCrypto();
		m_stream->Seek(position);
    }
    virtual void Write(char data) {                     
		doCrypto();
		m_stream->Write(data);
    }
private:
    void doCrypto() {}
private:
    Stream* m_stream;                    // 编译时无关,运行时根据new的具体对象决定调用什么方法
};

class BufferedStream:public Stream {
public:
    BufferedStream(Stream* stream){ this->m_stream = stream; }      
public:
    virtual char Read(int number) {                    
		doBuffered();
		m_stream->Read(number);
    }
    virtual void Seek(int position) {                  
		doBuffered();
		m_stream->Seek(position);
    }
    virtual void Write(char data) {                    
		doBuffered();
		m_stream->Write(data);
    }
private:
    void doBuffered() {}
private:
    Stream* m_stream;                    // 编译时无关,运行时根据new的具体对象决定调用什么方法
};    

int main()
{
    // 运行时装配
    Stream         *m1 = new FileStream();
    CryptoStream   *m2 = new CryptoStream(m1);
    BufferedStream *m3 = new BufferedStream(m2);        // m3既可以加密,又可以缓存
    delete s1; delete s2; delete s3;

    /*codes to do something... */
    delete s1; delete s2; delete s3;
    renturn 0;
}

五、 再再思考

CryptoStream和BufferedStream含有公共的部分m_stream,根据GOF的思想,含有的公共部分可以网上提,即可以将 Stream* m_stream 放在基类里,但是我们发现FileStream、NetworkStream、MemoryStream并不需要这个.由此,我们可以写个中间类,比如MidStream,如文中代码所述;此时,可以CryptoStream和BufferedStream让继承MidStream即可.

// 创建中间类,这里不做虚函数实现,也就无法创建对象
class MidStream:public Stream {
protected:
	Stream *m_stream;
};

六、要点总结

  1. 通过采用组合而非继承的手法,装饰者模式实现了在运行时动态扩展对象功能的呢理工,而且可以根据需要扩展多个功能,避免了使用继承带来的"灵活性差"和"多子类衍生问题".
  2. 装饰者类在接口上表现为IS-A的继承关系,但又在实现上表现为Has-A的组合关系;
  3. 装饰者模式的目的并非为了解决"多子类衍生的多继承"问题,而主要在于解决"主体类在多个方向上的扩展功能"——是为"装饰"的含义.

你可能感兴趣的:(C++,设计模式,c++,编程语言,设计模式)