本篇介绍了其他常用设计模式并对所有设计模式及相关设计原则进行了总结。
1 其他常用设计模式
1.1 桥模式(Bridge)
桥模式与装饰者模式类似,都是执行“单一职责”原则的模式。在某些情况下,有些类的实现可能包含多个维度的变化,例如一个信息发送器类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Messager{ public: virtual void Login(string username, string password)=0; virtual void SendMessage(string message)=0; virtual void SendPicture(Image image)=0; virtual void PlaySound()=0; virtual void DrawShape()=0; virtual void WriteText()=0; virtual void Connect()=0; virtual ~Messager(){} };
|
该类中第一部分方法需要调用第二部分方法,但每一部分方法都有不同的实现,比如PC平台下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| class PCMessagerBase : public Messager{ public: virtual void PlaySound(){ } virtual void DrawShape(){ } virtual void WriteText(){ } virtual void Connect(){ } };
class PCMessagerLite : public PCMessagerBase { public: virtual void Login(string username, string password){ PCMessagerBase::Connect(); } virtual void SendMessage(string message){ PCMessagerBase::WriteText(); } virtual void SendPicture(Image image){ PCMessagerBase::DrawShape(); } };
class PCMessagerPerfect : public PCMessagerBase { public: virtual void Login(string username, string password){ PCMessagerBase::PlaySound(); PCMessagerBase::Connect(); } virtual void SendMessage(string message){ PCMessagerBase::PlaySound(); PCMessagerBase::WriteText(); } virtual void SendPicture(Image image){ PCMessagerBase::PlaySound(); PCMessagerBase::DrawShape(); } };
|
而当我们想要加入一个移动平台时,移动平台的业务逻辑也需要全部重写,从而产生大量重复代码,并且在修改业务逻辑时,也要对每个平台的代码进行修改。
使用桥模式,将抽象部分(业务)和实现部分(平台)分离,使他们可以独立的变化。因此将业务和平台分离成两个基类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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(){} };
class MessagerImp{ public: virtual void PlaySound()=0; virtual void DrawShape()=0; virtual void WriteText()=0; virtual void Connect()=0; virtual MessagerImp(){} };
|
然后平台的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 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(){ } };
|
之后业务部分包含一个平台对象,就无需关注具体的平台是谁了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| 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(); } };
|
这样就实现了抽象和实现的解耦,每个类只具有单一的职责。
桥模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各自维度的变化,即“子类化”它们。桥模式有时候类似于多继承方案,但是多继承方案往往违背单一职责原则,因此复用性比较差。桥模式是比多继承方案更好的解决方法。
1.2 生成器模式(Builder)
生成器模式与工厂模式同属于对象创建模式,都是用来管理对象创建的模式。在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成,但由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在 一起的算法却相对稳定。生成器模式将一个复杂对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
以搭建房子为例,生成器模式会很好理解,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| class House{ };
class HouseBuilder { public: House* GetResult(){ return pHouse; } virtual ~HouseBuilder(){} protected: House* pHouse; virtual void BuildPart1()=0; virtual void BuildPart2()=0; virtual void BuildPart3()=0; virtual void BuildPart4()=0; virtual void BuildPart5()=0; };
class StoneHouse: public House{ };
class StoneHouseBuilder: public HouseBuilder{ protected: virtual void BuildPart1(){ } virtual void BuildPart2(){ } virtual void BuildPart3(){ } virtual void BuildPart4(){ } virtual void BuildPart5(){ } };
class HouseDirector{ public: HouseBuilder* pHouseBuilder; HouseDirector(HouseBuilder* pHouseBuilder){ this->pHouseBuilder=pHouseBuilder; } House* Construct(){ pHouseBuilder->BuildPart1(); for (int i = 0; i < 4; i++){ pHouseBuilder->BuildPart2(); } bool flag=pHouseBuilder->BuildPart3(); if(flag){ pHouseBuilder->BuildPart4(); } pHouseBuilder->BuildPart5(); return pHouseBuilder->GetResult(); } };
|
从上面的代码可见,Builder 模式主要用于“分步骤构建一个复杂的对象”。在这其中“分步骤”是一个稳定的算法,而复杂对象的各个部分则经常变化。
1.3 职责链模式(Chain of Resposibility)
职责链模式与组合模式、迭代器模式类似,都属于面向数据结构的设计模式。在软件设计中,一个请求可能被多个对象处理,但是每个请求在运行时只能有一个接收者,如果显式指定,将带来发送者和接收者之间的紧耦合。职责链模式将对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
代码实现也非常直观:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| #include <iostream> #include <string>
using namespace std;
enum class RequestType { REQ_HANDLER1, REQ_HANDLER2, REQ_HANDLER3 };
class Reqest { string description; RequestType reqType; public: Reqest(const string & desc, RequestType type) : description(desc), reqType(type) {} RequestType getReqType() const { return reqType; } const string& getDescription() const { return description; } };
class ChainHandler{ ChainHandler *nextChain; void sendReqestToNextHandler(const Reqest & req) { if (nextChain != nullptr) nextChain->handle(req); } protected: virtual bool canHandleRequest(const Reqest & req) = 0; virtual void processRequest(const Reqest & req) = 0; public: ChainHandler() { nextChain = nullptr; } void setNextChain(ChainHandler *next) { nextChain = next; } void handle(const Reqest & req) { if (canHandleRequest(req)) processRequest(req); else sendReqestToNextHandler(req); } };
class Handler1 : public ChainHandler{ protected: bool canHandleRequest(const Reqest & req) override { return req.getReqType() == RequestType::REQ_HANDLER1; } void processRequest(const Reqest & req) override { cout << "Handler1 is handle reqest: " << req.getDescription() << endl; } };
class Handler2 : public ChainHandler{ protected: bool canHandleRequest(const Reqest & req) override { return req.getReqType() == RequestType::REQ_HANDLER2; } void processRequest(const Reqest & req) override { cout << "Handler2 is handle reqest: " << req.getDescription() << endl; } };
class Handler3 : public ChainHandler{ protected: bool canHandleRequest(const Reqest & req) override { return req.getReqType() == RequestType::REQ_HANDLER3; } void processRequest(const Reqest & req) override { cout << "Handler3 is handle reqest: " << req.getDescription() << endl; } };
int main(){ Handler1 h1; Handler2 h2; Handler3 h3; h1.setNextChain(&h2); h2.setNextChain(&h3); Reqest req("process task ... ", RequestType::REQ_HANDLER3); h1.handle(req); return 0; }
|
职责链模式使多个对象都有处理请求的机会,从而避免了请求发送者和接受者的紧耦合。应用职责链模式可以使对象的指责分派更加灵活,可以在运行时动态的添加或者修改请求处理的职责。
1.4 享元模式(Flyweight)
在软件运行中,大量相同的对象会带来很大的运行开销,降低软件运行效率。享元模式通过共享技术减少运行时实例对象的个数从而提升性能。所谓共享技术就是使用一个对象工厂来管理许多虚拟对象,这些虚拟对象只要状态一样就只会存在一个,从而达到了减少对象数量的目的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| class Font { private: string key; public: Font(const string& key){ } };
class FontFactory{ private: map<string,Font* > fontPool; public: Font* GetFont(const string& key){ map<string,Font*>::iterator item=fontPool.find(key); if(item!=footPool.end()){ return fontPool[key]; } else{ Font* font = new Font(key); fontPool[key]= font; return font; }
} void clear(){ } };
|
1.5 备忘录模式(Memento)
备忘录模式与状态模式类似,属于应对状态变化的模式。在软件设计中,某些对象在状态转换过程中,由于某种需要可能要求程序能够回溯到对象之前的某个状态,如果使用一些公有接口来实现,则会暴露对象的实现细节。备忘录模式在不破坏封装的前提下,捕获一个对象的内部状态,并在对象外保存这个状态,这样就可以在之后需要的时候将对象恢复到原先保存的状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| class Memento { string state; public: Memento(const string & s) : state(s) {} string getState() const { return state; } void setState(const string & s) { state = s; } };
class Originator { string state; public: Originator() {} Memento createMomento() { Memento m(state); return m; } void setMomento(const Memento & m) { state = m.getState(); } };
int main() { Originator orginator; Memento mem = orginator.createMomento(); orginator.setMomento(memento); }
|
1.6 访问者模式(Visitor)
在软件构建过程中,由于需求变化,某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中更改,将会给子类带来繁重的变更负担,甚至破坏原有设计。访问者模式定义一个访问者对象来表示一个作用于某对象结构中各元素的操作,使得可以在不改变各元素类的前提下定义作用于这些元素的新操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| #include <iostream> using namespace std;
class Visitor;
class Element { public: virtual void accept(Visitor& visitor) = 0;
virtual ~Element(){} };
class ElementA : public Element { public: void accept(Visitor &visitor) override { visitor.visitElementA(*this); } };
class ElementB : public Element { public: void accept(Visitor &visitor) override { visitor.visitElementB(*this); } };
class Visitor{ public: virtual void visitElementA(ElementA& element) = 0; virtual void visitElementB(ElementB& element) = 0; virtual ~Visitor(){} };
class Visitor1 : public Visitor{ public: void visitElementA(ElementA& element) override{ cout << "Visitor1 is processing ElementA" << endl; } void visitElementB(ElementB& element) override{ cout << "Visitor1 is processing ElementB" << endl; } };
class Visitor2 : public Visitor{ public: void visitElementA(ElementA& element) override{ cout << "Visitor2 is processing ElementA" << endl; } void visitElementB(ElementB& element) override{ cout << "Visitor2 is processing ElementB" << endl; } }; int main() { Visitor2 visitor; ElementB elementB; elementB.accept(visitor); ElementA elementA; elementA.accept(visitor); return 0; }
|
1.7 解释器模式(Interpreter)
在软件构建过程中,如果某一特定领域的问题比较复杂,类似的结构不断重复出现,使用普通编程方式将面临非常频繁的变化,此时将特定领域的问题表达为某种语法规则下的句子,然后构建一个解释器来解释这样的句子,就可以解决问题。
中介者模式与适配器、外观、代理模式类似,是用于接口隔离的模式。在软件设计中,经常会出现多个对象互相关联交互的情况,对象之间经常会维持一种复杂的引用关系,如果遇到一些需求的更改,这样复杂的关系将面临不断地变化。中介者模式使用一个中介对象管理这种复杂关系,使这些对象之间不需要显式的引用彼此,避免相互交互的对象之间的紧耦合。
1.9 原型模式(Prototype)
当创建给定类的实例的过程很复杂或者代价很大时,就可以使用原型模式。原型模式允许通过复制现有的实例来创建性的实例。
2 设计模式总结
到此为止已经学习了全部常用的设计模式,但是模式并不代表是一成不变的,以上只是前人的经验总结出来的解决通用问题的通用技法,在具体任务中,最重要的还是利用面向对象的设计原则和思路,来使用适合当前任务的”模式“,这里的模式不一定是已经存在的模式,而是我们利用设计原则让复杂问题简单化的方法,是我们自己的”模式“,最好的模式就是忘掉模式,只记住原则。因此这里总结前面设计到的所有面向对象设计原则:
- 依赖倒置原则(DIP):高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定) 。抽象(稳定)不应该依赖于实现细节(变化) ,实现细节应该依赖于抽象(稳定)。
- 开放封闭原则(OCP):对扩展开放,对更改封闭。类模块应该是可扩展的,但是不可修改。
- 单一职责原则(SRP):一个类应该仅有一个引起它变化的原因。变化的方向隐含着类的责任。
- Liskov 替换原则(LSP):子类必须能够替换它们的基类(IS-A)。
- 接口隔离原则(ISP):不应该强迫客户程序依赖它们不用的方法。接口应该小而完备。
- 优先使用对象组合,而不是继承:继承在某种程度上破坏了封装性,子类父类耦合度高。而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低。
- 封装变化:使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合。
- 针对接口编程,而不是针对实现编程:客户程序无需获知对象的具体类型,只需要知道对象所具有的接口。减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案。
最后,所有设计原则都有一个共同的目标,就是管理变化,提高复用。