C++设计模式 之 “单一职责”模式:Decorator、Bridge
part 1 “单一职责”模式
在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任。
典型模式
Decorator
Bridge
part 2.1 Decorator 装饰模式
动机(Motivation)
#在某些情况下我们可能会“过度地使用继承来扩展对象的功能”,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。
#如何使“对象功能的扩展”能够根据需要来动态地实现?同时避免“扩展功能的增多”带来的子类膨胀问题?从而使得任何“功能扩展变化”所导致的影响将为最低?
模式定义
动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码 & 减少子类个数)。——《设计模式》GoF
代码实现 1 from 课程
抽象的主体,Stream类代码如下:
class Stream{ public: virtual char Read(int number)=0; virtual void Seek(int position)=0; virtual void Write(char data)=0; virtual ~Stream(){} };
Stream抽象基类的具体实现类之一 : FileStream类,代码如下:
class FileStream: public Stream{ public: virtual char Read(int number){ //读文件流 } virtual void Seek(int position){ //定位文件流 } virtual void Write(char data){ //写文件流 } };
Stream抽象基类的具体实现之二 : NetworkStream类,代码如下:
class NetworkStream :public Stream{ public: virtual char Read(int number){ //读网络流 } virtual void Seek(int position){ //定位网络流 } virtual void Write(char data){ //写网络流 } };
Stream抽象基类的具体实现之三 : MemoryStream类,代码如下:
class MemoryStream :public Stream{ public: virtual char Read(int number){ //读内存流 } virtual void Seek(int position){ //定位内存流 } virtual void Write(char data){ //写内存流 } };
DecoraterStream类:维持一个指向Stream的指针,并定义一个与Stream接口一致的接口。代码如下:
DecoratorStream: public Stream{ protected: Stream* stream;//... DecoratorStream(Stream * stm):stream(stm){ } };
具体职责之一,加密功能的实现,CryptoStream类代码如下:
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);//写文件流 //额外的加密操作... } };
具体职责之二,缓冲功能的实现,BufferedStream类代码如下:
class BufferedStream : public DecoratorStream{ Stream* stream;//... 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); }
UML结构
要点总结
#通过采用组合而非继承的手法, Decorator模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用继承带来的“灵活性差”和“多子类衍生问题”。
#Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类。
#Decorator模式的目的并非解决“多子类衍生的多继承”问题,Decorator模式应用的要点在于解决“主体类在多个方向上的扩展功能”——是为“装饰”的含义。
#如果类 A 继承类 B,同时类 A 又组合类 B,则类 A 有99%的可能是 Decorator 设计模式。
part 2.2 Bridge 桥模式
动机(Motivation)
#由于某些类型的固有的实现逻辑,使得它们具有两个变化的维度,乃至多个纬度的变化。
#如何应对这种“多维度的变化”?如何利用面向对象技术来使得类型可以轻松地沿着两个乃至多个方向变化,而不引入额外的复杂度?
模式定义
将抽象部分(业务功能)与实现部分(平台实现)分离,使它们都可以独立地变化。——《设计模式》GoF
代码实现一 from 课程
上层抽象类 Messager 类,负责系统的抽象,代码如下:
class Messager{ protected: MessagerImp* messagerImp;//... public: virtual void Login(string username, string password)=0; virtual void SendMessage(string message)=0; virtual void SendPicture(Image image)=0; virtual ~Messager(){} };
MessagerImp类:与 Messager 同层、负责实现部分。代码如下:
class MessagerImp{ public: virtual void PlaySound()=0; virtual void DrawShape()=0; virtual void WriteText()=0; virtual void Connect()=0; virtual MessagerImp(){} };
PCMessagerImp 类和 MobileMessagerImp 类继承于MessagerImp类,负责系统的功能实现部分。代码如下:
class PCMessagerImp : public MessagerImp{ public: virtual void PlaySound(){ //********** } virtual void DrawShape(){ //********** } virtual void WriteText(){ //********** } virtual void Connect(){ //********** } }; class MobileMessagerImp : public MessagerImp{ public: virtual void PlaySound(){ //========== } virtual void DrawShape(){ //========== } virtual void WriteText(){ //========== } virtual void Connect(){ //========== } };
MessagerLite 类和 MessagerPerfect 类负责系统的业务抽象,代码如下:
class MessagerLite :public Messager { public: virtual void Login(string username, string password){ messagerImp->Connect(); //........ } virtual void SendMessage(string message){ messagerImp->WriteText(); //........ } virtual void SendPicture(Image image){ messagerImp->DrawShape(); //........ } }; class MessagerPerfect :public Messager { public: virtual void Login(string username, string password){ messagerImp->PlaySound(); //******** messagerImp->Connect(); //........ } virtual void SendMessage(string message){ messagerImp->PlaySound(); //******** messagerImp->WriteText(); //........ } virtual void SendPicture(Image image){ messagerImp->PlaySound(); //******** messagerImp->DrawShape(); //........ } };
用户实现代码如下:
void Process(){ //运行时装配 MessagerImp* mImp=new PCMessagerImp(); Messager *m =new Messager(mImp); }
UML(一)
代码实现二 from 《大话设计模式》
#include <iostream> using namespace std; struct Software { virtual void run () = 0; }; struct Contact : public Software{ virtual void run () { cout << "通讯录" << endl; } }; struct Game : public Software { virtual void run () { cout << "游戏" << endl; } }; struct Brand { virtual void run () { _software->run(); } void setFunction(Software *software){ this->_software = software; } Software* _software; }; struct IPhone : public Brand{ virtual void run () override { cout << "IPhone "; _software->run(); } }; struct Mi : public Brand { //不实现run(),沿用父类虚函数。 }; int main() { Mi *phone1 = new Mi(); IPhone *phone2 = new IPhone(); phone1->setFunction(new Contact()); phone2->setFunction(new Game()); phone1->run(); phone2->run(); return 0; }
UML(二)
要点总结
#Bridge模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各自纬度的变化,即“子类化”它们。
#Bridge模式有时候类似于多继承方案,但是多继承方案往往违背单一职责原则(即一个类只有一个变化的原因),复用性比较差。Bridge模式是比多继承方案更好的解决方法。
#Bridge模式的应用一般在“两个非常强的变化维度”,有时一个类也有多于两个的变化维度,这时可以使用Bridge的扩展模式。
#继承转组合:一个伟大的手段。
#部分 override 纯虚函数的类,依旧是抽象类(C++语法是这样规定的)。