星宿网站建设,行业门户网站是什么,建筑公司网站背景图,网站开发源代码修改本篇将会介绍Decorator 装饰模式#xff0c;它是属于一个新的类别#xff0c;按照C设计模式_03_模板方法Template Method中介绍的划分为“单一职责”模式。 “单一职责”模式讲的是在软件组件的设计中#xff0c;如果责任划分的不清晰#xff0c;使用继承得到的结果往往是随…本篇将会介绍Decorator 装饰模式它是属于一个新的类别按照C设计模式_03_模板方法Template Method中介绍的划分为“单一职责”模式。 “单一职责”模式讲的是在软件组件的设计中如果责任划分的不清晰使用继承得到的结果往往是随着需求的变化子类急剧膨胀同时充斥着重复代码这时候的关键是划清责任。
典型的模式包括Decorator 和 Bridge。这是因为这两种模式在责任的问题上表现得特别突出但不意味着其他模式没有责任问题。 本篇主要介绍Decorator 装饰模式 文章目录 1. 代码演示Decorator 装饰模式1.1 基于继承的常规思维处理1.2 基于组合关系的重构优化1.3 采用Decorator 装饰模式的实现 2. 动机Motivation3. 模式定义4. 结构Structure5. 要点总结6. 其他参考博文 1. 代码演示Decorator 装饰模式
首先结合实际场景和代码进行分析 设计场景设计一些IO库涉及一些流操作针对流的操作我们具有很多需求比如文件流、网络流、内存流等也有对流进行加密进行缓存等操作。
1.1 基于继承的常规思维处理
首先我们可能会想到流的设计首先需要一个基类基类有一些例如Read()、Seek()、Write()等方法的公共操作作为纯虚函数放到Stream基类中。文件流FileStream继承自Stream基类override Read()、Seek()、Write()等虚函数。网络流NetworkStream、内存流MemoryStream操作也是类似的
当我们进行加密操作时可以发现首先我们需要对流有个主体的操作才能加密我们对其中的文件流进行加密首先去继承文件流FileStream在Read()中进行额外的加密操作调用基类的方法FileStream::Read(number);Seek()、Write()方法也是在其前面做额外的加密操作。
上述过程是对文件流进行加密操作对网络流、内存流也有加密需求也需要重复上面在文件流加密中的动作我们可以发现加密操作是一样的只是流的读取操作等不一样。
相应的对流的缓冲操作BufferedFileStream也需要考虑文件流、网络流、内存流的操作代码并未详细写
对文件流既加密又缓冲的双重操作CryptoBufferedFileStream此处是继承了FileStream当然有些朋友可以直接在这里继承一个CryptoFileStream也是可以的。此处进行额外的加密和缓冲操作。
//业务操作
class Stream{
publicvirtual 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 CryptoFileStream :public FileStream{
public:virtual char Read(int number){//额外的加密操作...FileStream::Read(number);//读文件流}virtual void Seek(int position){//额外的加密操作...FileStream::Seek(position);//定位文件流//额外的加密操作...}virtual void Write(byte 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(byte 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(byte data){//额外的加密操作...MemoryStream::Write(data);//写内存流//额外的加密操作...}
};class BufferedFileStream : public FileStream{//...
};class BufferedNetworkStream : public NetworkStream{//...
};class BufferedMemoryStream : public MemoryStream{//...
}class CryptoBufferedFileStream :public FileStream{
public:virtual char Read(int number){//额外的加密操作...//额外的缓冲操作...FileStream::Read(number);//读文件流}virtual void Seek(int position){//额外的加密操作...//额外的缓冲操作...FileStream::Seek(position);//定位文件流//额外的加密操作...//额外的缓冲操作...}virtual void Write(byte data){//额外的加密操作...//额外的缓冲操作...FileStream::Write(data);//写文件流//额外的加密操作...//额外的缓冲操作...}
};void Process(){//编译时装配CryptoFileStream *fs1 new CryptoFileStream();BufferedFileStream *fs2 new BufferedFileStream();CryptoBufferedFileStream *fs3 new CryptoBufferedFileStream();}针对上述代码我们来分析是否存在问题。 整理设计类图结构Stream是基类FileStream、NetworkStream、MemoryStream均继承自Syream并对Read()、Seek()、Write()等进行了override并且对各个基类进行了CryptoFileStream、BufferedFileStream和CryptoBufferedFileStream。 大家可以考虑下当存在n个流类型的需求时这个类的规模有多少呢
Stream记作1FileStream、NetworkStream、MemoryStream…等n种流类型对于每个流类型有CryptoFileStream、BufferedFileStream和CryptoBufferedFileStream…等m种操作。总共加起来的个数为1n(m!/2)(很多人很容易想到其为1nm但实际在操作中会有既加密又等组合的情况实际就是n(m(m-1)(m-2)…1)数学中对应的就是m的阶乘除以2)。这个类的规模就会变得十分大。
再研究上面的代码发现对于加密操作不管是哪个类方法的加密方法都是一样的大量重复的代码就造成了代码冗余。
1.2 基于组合关系的重构优化
为了消除重复带来的代码冗余对代码进行重构。
利用组合关系替代继承关系设计原则中也有提到组合由于继承在重构中的规则当一个变量的声明类型都是某一个类型的子类的时候那么在声明时就将其声明为该类型
例如上面代码转化组合关系时从某一个类的FileStream* stream改定义为Stream* stream;利用多态在运行时定义为子类型既可以实现方法Stream* stream new FileStream();这也就使得编译时的东西变为运行时的东西这里可以悟出一个设计模式的真谛就是“编译时一样运行时不一样”。
经过对代码的重构就将class CryptoFileStream :public FileStream、class CryptoNetworkStream : :public NetworkStream和class CryptoMemoryStream : public MemoryStream的3段重复性代码重构为以下的一段代码。
class CryptoStream: public Stream {Stream* stream;//...public:CryptoStream(Stream* stm):stream(stm){}virtual char Read(int number){//额外的加密操作...stream-Read(number);//读文件流}virtual void Seek(int position){//额外的加密操作...stream::Seek(position);//定位文件流//额外的加密操作...}virtual void Write(byte data){//额外的加密操作...stream::Write(data);//写文件流//额外的加密操作...}
};以上代码中Read()、Seek()、Write()均采用虚函数的方式这是因为遵循流的规范才能使用虚函数因此class CryptoStream需要继承public Stream是完善Read()、Seek()、Write()虚函数的接口规范也就是Stream基类定义了CryptoStream的接口规范。
class CryptoStream: public Stream {
virtual char Read(int number){...}
virtual void Seek(int position){...}
virtual void Write(byte data){...}
}这时就有一个特别有意思的变化既有一个Stream基类又有Stream* stream的字段。
同样的道理class BufferedStream也可以重构为以下代码
class BufferedStream : public Stream{Stream* stream;//...public:BufferedStream(Stream* stm):stream(stm){}//...
};写到此处最初代码中的问题已经得到了极大的缓解真正使用的时候。
void Process(){//运行时装配FileStream* s1new FileStream();CryptoStream* s2new CryptoStream(s1);BufferedStream* s3new BufferedStream(s1);//既加密又缓存 BufferedStream* s4new BufferedStream(s2);
}编译时不存在加密文件流缓存文件流的类但是运行时可以通过组合的方式把他们装配起来满足需求。
这样最终的代码为
//业务操作
class Stream{publicvirtual 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 CryptoStream: public Stream {Stream* stream;//...public:CryptoStream(Stream* stm):stream(stm){}virtual char Read(int number){//额外的加密操作...stream-Read(number);//读文件流}virtual void Seek(int position){//额外的加密操作...stream::Seek(position);//定位文件流//额外的加密操作...}virtual void Write(byte data){//额外的加密操作...stream::Write(data);//写文件流//额外的加密操作...}
};class BufferedStream : public Stream{Stream* stream;//...public:BufferedStream(Stream* stm):stream(stm){}//...
};void Process(){//运行时装配FileStream* s1new FileStream();CryptoStream* s2new CryptoStream(s1);BufferedStream* s3new BufferedStream(s1);BufferedStream* s4new BufferedStream(s2);}到这里问题已经解决的差不多了这个版本在很多类库中也是比较常见的这样其实已经OK了
1.3 采用Decorator 装饰模式的实现
如果根据马丁福勒经典意义上的重构理论重构理论中还有一条“如果某一个类它有多个子类具有同样的字段时应该往上提”Stream* stream;如果放到class Stream但是在其子类class FileStream并不需要这个字段FileStream本身就是主体并不需要Stream* stream字段。
这个时候怎么去做呢此时需要一个中间类这个中间类就是class DecoratorStream。
class DecoratorStream: public Stream{
protected:Stream* stream;//...DecoratorStream(Stream * stm):stream(stm){}};整体代码为
//业务操作
class Stream{publicvirtual 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 DecoratorStream: public Stream{
protected:Stream* stream;//...DecoratorStream(Stream * stm):stream(stm){}};class CryptoStream: public DecoratorStream {public:CryptoStream(Stream* stm):DecoratorStream(stm){}virtual char Read(int number){//额外的加密操作...stream-Read(number);//读文件流}virtual void Seek(int position){//额外的加密操作...stream::Seek(position);//定位文件流//额外的加密操作...}virtual void Write(byte data){//额外的加密操作...stream::Write(data);//写文件流//额外的加密操作...}
};class BufferedStream : public DecoratorStream{Stream* stream;//...public:BufferedStream(Stream* stm):DecoratorStream(stm){}//...
};void Process(){//运行时装配FileStream* s1new FileStream();CryptoStream* s2new CryptoStream(s1);BufferedStream* s3new BufferedStream(s1);BufferedStream* s4new BufferedStream(s2);}梳理一下就会发现很巧妙FileStream、NetworkStream和MemoryStream始终是没有动的他们是可以单独行驶行为的但是加密流必须传一个流的对象。这些操作的本质上就是扩展这就是Decorator 的含义装饰是附着在其他上的操作。
Stream基类FileStream、NetworkStream、MemoryStream作为Stream的子类也是不变设计了DecoratorStreamCryptoStream和BufferedStream继承自DecoratorStream。这种情况下类的规模就是1nm对比前面1n(m!/2)减少了很多。
对两者进行分析发现出现1n(m!/2)的规模是由于重复代码的多次使用继承的不良使用我们现在想一下CryptoFileStream和BufferedFileStream一定要继承FileStream来完成加密和缓存操作吗不是的组合更好class DecoratorStream: public Stream{ protected: Stream* stream;//... }中的Stream* stream; 是设计的核心就是用组合的方式来引出未来多态的支持。
2. 动机Motivation 在某些情况下我们可能会“过度地使用继承来扩展对象的功能”由于继承为类型引入的静态特质使得这种扩展方式缺乏灵活性并且随着子类的增多扩展功能的增多各种子类的组合扩展功能的组合会导致更多子类的膨胀。 例如在decorator1.cpp代码中class CryptoFileStream中的FileStream::Read(number);就是静态特质是没有变化的可能性的这是由继承实现的而在decorator3.cpp代码中class CryptoStream中的stream-Read(number);中stream是基类的多态指针就是有变化的这也就是动态特质这是由组合实现的 如何使“对象功能的扩展”能够根据需要来动态地实现同时避免“扩展功能的增多”带来的子类膨胀问题从而使得任何“功能扩展变化”所导致的影响将为最低
3. 模式定义
动态组合地给一个对象增加一些额外的职责。就增加功能而言Decorator模式比生成子类继承更为灵活消除重复代码 减少子类个数。 ——《设计模式》GoF
4. 结构Structure 上图是《设计模式》GoF中定义的Decorator 装饰模式的设计结构。结合上面的代码看图中最重要的是看其中稳定和变化部分也就是下图中红框和蓝框框选的部分。
5. 要点总结 通过采用组合而非继承的手法 Decorator模式实现了在运行时动态扩展对象功能的能力而且可以根据需要扩展多个功能。避免了使用继承带来的“灵活性差”和“多子类衍生问题”。 Decorator类在接口上表现为is-a Component的继承关系即Decorator类继承了Component类所具有的接口。但在实现上又表现为has-a Component的组合关系即Decorator类又使用了另外一个Component类。
这是一种同时继承与组合的模式以后如果看到一个类它的父类是一个类例如父类是Stream又有一个字段是Stream* stream这个时候就高度怀疑是Decorator 装饰模式。有的时候看不到内部字段至少可以从类的外部接口进行推测父类是一个类构造器的参数也是某一个类型例如class CryptoStream: public DecoratorStream {public:CryptoStream(Stream* stm):DecoratorStream(stm){ }}
Decorator模式的目的并非解决“多子类衍生的多继承”问题Decorator模式应用的要点在于解决“主体类在多个方向上的扩展功能”——是为“装饰”的含义。
6. 其他参考博文
Decorator 装饰模式