设计模式---单一职责模式之装饰模式(Decorator)
前提:"单一职责"模式
在软件组件的设计中,如果责任划分的不清晰,使用继承,得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任
典型模式(表现最为突出)
装饰模式Decorator
桥接模式Bridge
一:装饰模式
(一)概念
装饰模式又叫做包装模式。通过一种对客户端透明的方式来扩展对象的功能,是继承关系的一个替换方案。
装饰模式就是把要添加的附加功能分别放在单独的类中,并让这个类包含它要装饰的对象,当需要执行时,客户端就可以有选择的,顺序的使用装饰功能包装对象
(二)动机
在某些情况下我们可能会“过度的使用继承来扩展对象的功能”,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;
并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多的子类膨胀。
即我们需要找到一种方式,实现功能可通过非继承方式扩展,但是接口不发生变化的类。
如何使“对象功能的扩展”能够根据需要来动态地实现?同时避免“扩展功能的增多”带来的子类膨胀问题?
从而使得任何“功能扩展变化”所导致的影响将为最低?
(三)原代码讲解(流操作)
文件流,内存流,网络流,有对流进行加密,缓存等操作
//业务操作
class Stream{ //公共基类,含有共有方法
public:
virtual char Read(int number)=;
virtual void Seek(int position)=;
virtual void Write(char data)=; 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(); }
出现的问题:
需要的类的个数是:
1+n+n*(m+m-1+...+2+1)
其中n是我们流的个数,m是我们的操作类型个数。
我们上面的n=,m=2所以需要的类是13个
问题的原因
除了其中的具体操作,例如:文件读取,网络读取等不同,我们发现所有的加密和缓存操作都是一样的方法,出现大量代码冗余,
(四)改进版本一(组合代替继承)
//扩展操作
class CryptoFileStream{
FileStream* stream;
public:
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 CryptoNetworkStream{
NetworkStream* stream;
public:
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 CryptoMemoryStream{
NetworkStream* stream;
public:
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 CryptoFileStream{
Stream* stream; //使用基类,消除了编译时依赖
public:
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 CryptoNetworkStream{
Stream* stream;
public:
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 CryptoMemoryStream{
Stream* stream;
public:
virtual char Read(int number){ //额外的加密操作...
stream->Read(number);//读内存流
}
virtual void Seek(int position){
//额外的加密操作...
stream->Seek(position);//定位内存流
//额外的加密操作...
}
virtual void Write(byte data){
//额外的加密操作...
stream->Write(data);//写内存流
//额外的加密操作...
}
};
我们发现这3个类一样,除了类名之外都相同,所以我们可以进行合并,消除重复性
//扩展操作
class CryptoFileStream{
Stream* stream;
public:
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 BufferedFileStream{ //所有相同操作都将去重,只保留一个
//...
};
同时发现一个问题,我们从继承转组合以后的虚函数哪来的?我们还是要遵循留的规范,即基类为我们设置的接口虚函数。我们还是需要进行继承,完善接口规范,不过只需要继承流的基类即可
//扩展操作
class CryptoStream :public Stream{
Stream* stream;
public:
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{
//...
};
下面是全部修改代码
//业务操作
class Stream{ public:
virtual char Read(int number)=;
virtual void Seek(int position)=;
virtual void Write(char data)=; 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* s1=new FileStream();
CryptoStream* s2=new CryptoStream(s1); BufferedStream* s3=new BufferedStream(s1);
//上面对s2加密了,我们再进行s2的缓存,实现了既加密又缓存
BufferedStream* s4=new BufferedStream(s2);
}
运行时装配:
我们编译时不存在文件加密,网络加密,文件缓存,文件加密缓存等操作,我们在运行时对其进行组合装配起来满足我们的需求
另外注意:
根据重构中所说:当我们类中含有重复字段和方法,我们应该将其提到前面基类中去,这里我们若是将Stream* stream提到Stream基类中去,会发现在主体类中会包含这个不需要的字段,所以这个时候我们应该设计一个中间基类。这时就引用出来了装饰模式
(五)改进版本二(使用装饰模式<中间基类>)
//扩展操作
//中间类
class DecoratorStream: public Stream{
protected:
Stream* stream;
public:
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{
public:
BufferedStream(Stream* stm):DecoratorStream(stm){ }
//...
};
最终规模是:+n++m
全部代码
//业务操作
class Stream{ public:
virtual char Read(int number)=;
virtual void Seek(int position)=;
virtual void Write(char data)=; 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){
//写内存流
} }; //扩展操作 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{ public:
BufferedStream(Stream* stm):DecoratorStream(stm){ }
//...
}; void Process(){ //运行时装配
FileStream* s1=new FileStream(); CryptoStream* s2=new CryptoStream(s1); BufferedStream* s3=new BufferedStream(s1); BufferedStream* s4=new BufferedStream(s2);
}
以组合的方式来支持未来多态的变化
(六)模式定义
动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码 & 减少子类个数)。
——《设计模式》GoF
(七)类图(结构)
(八)要点总结
1.通过采用组合而非继承的手法, Decorator模式实现了在运行时 动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用继承带来的“灵活性差”和“多子类衍生问题”。
2.Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上又 表现为has-a Component的组合关系,即Decorator类又使用了 另外一个Component类。
DecoratorStream: public Stream{
protected:
Stream* stream;//... DecoratorStream(Stream * stm):stream(stm){ } };
若是程序中一个类的父类和他的字段类是同一个类的,那么他有极大的几率是Decorator模式。因为一般继承就不组合,组合就不继承
而我们这里的继承是为了接口的规范,组合是为了将来支持具体实现类
3.Decorator模式的目的并非解决“多子类衍生的多继承”问题, Decorator模式应用的要点在于解决“主体类在多个方向上的扩展 功能”——是为“装饰”的含义。
主体操作和扩展操作应该分开分之继承
(九)案例实现(装饰车)
//业务规范
class Car
{
public:
virtual void show() = ;
virtual ~Car(){}
};
//主体类
class RunCar :public Car
{
public:
virtual void show()
{
cout << "running" << endl;
}
}; class SwimCar :public Car
{
public:
virtual void show()
{
cout << "swimming" << endl;
}
}; class FlyCar :public Car
{
public:
virtual void show()
{
cout << "flying" << endl;
}
};
//装饰中间类
class DecoratorCar : public Car
{
protected:
Car * car;
public:
DecoratorCar(Car* c) :car(c){}
};
//扩展操作类
class EquipEngine :public DecoratorCar
{
public:
EquipEngine(Car* c) :DecoratorCar(c)
{
} virtual void show()
{
cout << "equip engine can run fast" << endl;
car->show();
}
}; class EquipWing :public DecoratorCar
{
public:
EquipWing(Car* c) :DecoratorCar(c)
{
} virtual void show()
{
cout << "equip wing can fly" << endl;
car->show();
}
}; class EquipPaddle :public DecoratorCar
{
public:
EquipPaddle(Car* c) :DecoratorCar(c)
{
} virtual void show()
{
cout << "equip paddle can swing" << endl;
car->show();
}
};
int main()
{
SwimCar* car = new SwimCar();
EquipPaddle *scar = new EquipPaddle(car);
EquipEngine *ecar = new EquipEngine(scar);
EquipWing *wcar = new EquipWing(ecar); wcar->show();
system("pause");
return ;
}
设计模式---单一职责模式之装饰模式(Decorator)的更多相关文章
- 设计模式---单一职责模式之桥模式(Bridge)
一:概念 Bridge模式又叫做桥接模式,其实基于类的最小设计原则,通过使用封装,聚合以及继承等行为来让不同的类承担不同的责任他的主要特点是吧抽象与行为实现分离开来,从而可以保持各部分的独立性以及一对 ...
- C++设计模式 之 “单一职责”模式:Decorator、Bridge
part 1 “单一职责”模式 在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任. 典型模式 Decorato ...
- 学习记录:《C++设计模式——李建忠主讲》4.“单一职责”模式
单一职责模式:在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任. 典型模式:装饰模式(Decorator).桥 ...
- 23种设计模式 - 单一职责(Decorator - Bridge)
其他设计模式 23种设计模式(C++) 每一种都有对应理解的相关代码示例 → Git原码 ⌨ 单一职责 在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀, ...
- 设计模式23---设计模式之装饰模式(Decorator)(结构型)
1.装饰模式讲解 1.1定义 动态的给一个对象添加一些额外的职责.就增加功能来说,装饰模式比生成子类更加灵活. 1.2装饰模式要点 透明的给一个对象增加功能,换句话说就是要给一个对象增加功能,但是不能 ...
- 设计模式:代理模式 vs 装饰模式
参考文章:https://www.cnblogs.com/luoxn28/p/5535877.html 代理模式和装饰模式非常类似,甚至代码都类似. 二者最主要的区别是: 代理模式中,代理类对被代理的 ...
- 编程模式之装饰模式(Decorator)
装饰模式由四个角色组成:抽象组件角色,抽象装饰者角色,具体组件角色,具体装饰者角色. 抽象组件角色:给出一个抽象接口,以规范"准备接受附加功能"的对象. 抽象装饰者角色:持有一个组 ...
- 设计模式-09装饰模式(Decorator Pattern)
1.模式动机 一般有两种方式可以实现给一个类或对象增加行为: 继承机制:使用继承机制是给现有类添加功能的一种有效途径,通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法.但是这种方法是 ...
- JAVA设计模式之单一职责原则
概念: 就一个类而言应该只有一个因其他变化的原因. 流程: 问题由来:设类或接口类C负责两个不同不同的职责:职责T1,职责T2.当由于职责T1需求改变进而需要修改类C时,可能导致职责T2收到不可预知的 ...
随机推荐
- Alpha冲刺之事后诸葛亮
组长博客 作业博客 项目Postmortem 设想和目标 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 我们的软件针对的是福大学子来到食堂会犹豫不决无法决定吃什么 ...
- PSexec以及xcopy的简单使用
1. 远程执行命令. 有时候不想远程但是想执行一些命令, 比较简单的方法是: 下载systeminternals 然后解压缩后可以讲目录放到path环境变量中 然后打开命令行工具 输入 如下的命令 p ...
- Qt__QMessageBox
转自豆子空间 显示窗口 Qt提供了五个类似的接口,用于显示类似的窗口. QMessageBox::information(NULL, "Title", "Content& ...
- ES6 Set & Map
ES6 Set & Map OK ES6 Map https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Globa ...
- D3.js v5 Tutorials
D3.js v5 Tutorials D3.js v5 教程 https://github.com/d3/d3/blob/master/API.md CHANGES https://github.co ...
- js timeout
setTimeOut(“”,毫秒):調用函數時,不是立刻執行,而是間隔一定的時間后在執行: clearTimeOut():清除計時器
- mysql case when 判断null
select name,case WHEN m.NAME is null THEN '' else m.NAME end NAME1 from sys_users
- Java 关键字final的一小结
* final类不能被继承,没有子类,final类中的方法默认是final的. * final方法不能被子类的方法覆盖,但可以别继承 (方法) * final 成员变量 表示常量,只能被赋值一 ...
- 【BZOJ3668】【NOI2014】起床困难综合症(贪心)
[NOI2014]起床困难综合症(贪心) 题面 Description 21 世纪,许多人得了一种奇怪的病:起床困难综合症,其临床表现为:起床难,起床后精神不佳.作为一名青春阳光好少年,atm 一直坚 ...
- linux-shell数据重定向详细分析
在了解重定向之前,我们先来看看linux 的文件描述符.linux文件描述符:可以理解为linux跟踪打开文件,而分配的一个数字,这个数字有点类似c语言操作文件时候的句柄,通过句柄就可以实现文件的读写 ...