原型模式(Prototype)原型模式是一种创建型设计模式,其功能为复制一个运行时的对象,包括对象各个成员当前的值。而代码又能保持独立性。场景举例假设这样一个场景,孙悟空出世,在花果山经历与猴孙的生死离别后,拜菩提老祖为师。历经多年,学的一身本领。此时的悟空拔出一个毫毛,变出一个自己。分析此时悟空变出的另一个自己,自然要与现在的年龄、身高、体重等多个参数一致。如果不加设计,这些代码可能散落在客户端函数。假如过了十年继续施展分身,又需重新增加这些代码。日积月累,这些终将成为晦涩而又难以维护的“一坨”。实现对于上述场景就有必要引入原型模式,原型模式的设计也是比较简单的。只需要在类的设计时增加一个clone接口,用于返回当前对象this指针。客户端在使用时,只需要对象的clone接口,就能拿到当前对象的各个成员值。类图原型模式通过原型模式基类规范具备复制接口的子类实现。源码#include <iostream> #include <string> usingnamespacestd; class CPrototype { public: CPrototype() {} ~CPrototype() {} virtual CPrototype* Clone() = 0; }; class CWukong : CPrototype { public: CWukong() : mAge(0), mHeightCM(100), mHair(10000), mLockRing(0), mFightCapacity (20) { } CWukong(CWukong *rhs) { mName = rhs->mName; mAge = rhs->mAge; mHeightCM = rhs->mHeightCM; mHair = rhs->mHair; mLockRing = rhs->mLockRing; mFightCapacity = rhs->mFightCapacity; } virtual ~CWukong() {} void SetName(string value) { mName = value; } void AddAge(int value) { mAge += value; } void SetHeight(int value) { mHeightCM = value; } void SetHair(int value) { mHair = value; } void PutLockRing() { mLockRing = 1; } void RemoveLockRing() { mLockRing = 0; } void SetFightCapacity(int value) { mFightCapacity = value; } string GetName() { return mName; } int GetAge() { return mAge; } int GetHeight() { return mHeightCM; } int GetHair() { return mHair; } int GetLockRing() { return mLockRing; } int GetFightCapacity() { return mFightCapacity; } CWukong* Clone() { returnnew CWukong(*this); } private: string mName; int mAge; int mHeightCM; int mHair; bool mLockRing; int mFightCapacity; }; static void learn_skills(CWukong *pMonkey) { //After 11 years of study, Wukong's parameters change pMonkey->SetName("Wukong.Sun"); pMonkey->AddAge(11); pMonkey->SetHeight(150); pMonkey->SetFightCapacity(80); } static void show_params(CWukong *pMonkey) { cout << ">> " << pMonkey->GetName() << endl; cout << "Age: " << pMonkey->GetAge() << endl; cout << "Height(cm): " << pMonkey->GetHeight() << endl; cout << "Hair: " << pMonkey->GetHair() << endl; cout << "LockRing: " << pMonkey->GetLockRing() << endl; cout << "FightCapacity: " << pMonkey->GetFightCapacity() << endl; cout << "\n" << endl; } int main (int argc, char *argv[]) { CWukong *theWukong = new CWukong(); //Before Learning, show params cout << "Before Learning" << endl; show_params(theWukong); //At the age of five, he went to learn skills theWukong->AddAge(5); theWukong->SetHeight(120); learn_skills(theWukong); //Return after Learning, show params show_params(theWukong); //Show multitasking skills cout << "Clone Wukong" << endl; CWukong *theWukong2 = theWukong->Clone(); show_params(theWukong2); delete theWukong2; delete theWukong; return0; }输出Before Learning >> Age: 0 Height(cm): 100 Hair: 10000 LockRing: 0 FightCapacity: 20 >> Wukong.Sun Age: 16 Height(cm): 150 Hair: 10000 LockRing: 0 FightCapacity: 80 Clone Wukong >> Wukong.Sun Age: 16 Height(cm): 150 Hair: 10000 LockRing: 0 FightCapacity: 80实现流程首先需要在父类定义 克隆(clone) 纯虚接口,子类需要在此接口中返回新创建的子类对象。子类中,增加一个以此类对象为参数的构造函数。用于保存当前所有成员变量值到新创建的对象中。子类的克隆接口,创建新的对象时,需要显示调用具备子类对象参数的构造函数。保证新的对象中,所有的成员都被初始化。总结原型模式能够克隆一个对象,而类之间无需耦合。客户代码在复制复杂的对象时,也更加方便。此接口可重复使用,让代码逻辑更加清晰易懂。
单例模式(Singleton)介绍单例模式是创建型设计模式,即用于创建对象的设计。其能够保证当前系统仅存在一个实例,并提供获取该实例的接口供客户端使用。(即创建对象禁止使用new,而是通过类的接口返回一个已经存在的对象。当强行new操作时,编译报错)单例模式的实现较为简单,部分专家并不认为其是一种设计,而是一种编程技巧。意义既然存在单例模式,那么其存在的意义是什么呢?又或者说能解决什么问题?1.减少对象多次创建与销毁,节省系统时间与资源。通常局部代码使用对象时,通常临时创建,用完释放。若使用的对象占用资源很少,还看不出什么影响。假设是一个很庞大的类,其创建和销毁对象就会耗时且浪费资源。若多个任务都这么使用,统计起来也会是一笔没有必要的浪费。2.保证系统资源被统一管理。在系统资源共享的情况下,每个任务都可以随意使用当前 资源。如果想了解共享资源被哪些任务使用时,难以实现。若设计对共享资源进行管理的单例类,所有的任务都只能通过单例类来访问共享资源。就可以实现此种需求。实现在上述介绍中,已经大致描述了单例的特点:创建唯一的实例 。实现方法,将默认构造函数声明为私有,防止其他任务在外部使用new创建实例。提供获取实例的接口。实现方法,新建一个静态类成员函数,该函数能够调用构造函数来创建对象,并缓存到静态成员中并返回。其他任务通过此函数获取到的便都是同一个实例。类图单例模式代码#include <iostream> #include <unistd.h> #include <thread> usingnamespacestd; class CSingleton { public: ~CSingleton() {} static CSingleton *getInstance(string value); string getValue() { return mValue; } private: CSingleton(conststring value): mValue(value) { } string mValue; }; CSingleton* CSingleton::getInstance(string value) { static CSingleton mObj(value); return &mObj; } void threadFirst() { CSingleton* pSingleton = CSingleton::getInstance("first"); cout << pSingleton->getValue() << "\n"; } void threadSecond() { CSingleton* pSingleton = CSingleton::getInstance("second"); cout << pSingleton->getValue() << "\n"; } void threadThird() { CSingleton* pSingleton = CSingleton::getInstance("third"); cout << pSingleton->getValue() << "\n"; } int main(int argc, char *argv[]) { thread t1(threadFirst); thread t2(threadSecond); thread t3(threadThird); t1.join(); t2.join(); t3.join(); return0; }输出:third third third在三个子线程创建实例时,分别向构造函数传入了"first"、"second"、"third",实际输出时都是"third"。说明三个线程使用的都为同一个实例。总结单例模式目前使用的较为流行,其实现方法也比较简单。当遇到需要管理公共资源时,可以采用单例模式。
组合模式能够体现各个对象之间的层次关系,将所有对象设计为同一种类型,从而忽略组合与个体的差异,统一管理起来。意义组合模式将所有的对象统一管理,并按照树形排列起来。用户能够按照结构顺序来查询指定位置的对象属性。应用场景公司的结构顺序。公司存在多个部门,部门内又存在多个小组,小组内存在多个成员。特殊类型的文件操作。类似于Xml类型的文本类型,其内部需要实现指定节点的增、删、改、查功能。场景设计一套层次关系,实现指定位置插入、删除节点的操作。类图组合模式CTree: 统一的基类,定义统一的接口Add、Remove,由子类实现添加删除具体节点操作。CLeaf: 叶子类,其不再存在子组件的节点。CTrunk: 枝干类,还存在子组件的节点。效果客户端接口// root // / \ \ // / \ \ // branch1 branch2 leaf // / \ \ // / \ \ // branch1_1 leaf1_1 leaf2_1 int main(int argc, char *argv[]) { CTrunk theRoot("root"); CTrunk theBranch1("branch1"); CTrunk theBranch2("branch2"); CTrunk theBranch1_1("branch1_1"); CLeaf theLeaf("leaf"); CLeaf theLeaf1_1("leaf1_1"); CLeaf theLeaf2_1("leaf2_1"); theBranch1.Add(&theBranch1_1); theBranch1.Add(&theLeaf1_1); theBranch2.Add(&theLeaf2_1); theRoot.Add(&theBranch1); theRoot.Add(&theBranch2); theRoot.Add(&theLeaf); theRoot.ShowAllBranch(&theRoot); //theRoot.ShowBranch(); return 0; }输出branch1 branch1_1 leaf1_1 branch2 leaf2_1 leaf 具体实现基类接口定义对外接口,包括增加、删除节点接口。class CTree { public: CTree(): mParent(0), mFirstChild(0), mLastChild(0), mPrev(0), mNext(0) {} virtual ~CTree() {} virtual CTree* Add(CTree *pNode); virtual int Remove(CTree *pNode); virtual string GetName(); virtual bool IsTrunk() const; CTree *mParent; CTree *mFirstChild; CTree *mLastChild; CTree *mPrev; CTree *mNext; };叶子类叶子类无需增加子节点。class CLeaf { public: explicit CLeaf(string Name); ~CLeaf(); string GetName(); private: string mName; };枝干类枝干类需要实现增加节点接口,通过链表将各个节点连接起来。class CTrunk : public CTree { public: explicit CTrunk(string Name); ~CTrunk(); CTree* Add(CTree *pNode); int Remove(CTree *pNode); string GetName(); bool IsTrunk() const; void ShowBranch(); void ShowAllBranch(CTree *p); private: string mName; };枝干类增加节点。每个枝干类内部维护一套双向链表,用于维护当前类增加的节点。CTree* CTrunk::Add(CTree *pNode) { if (NULL == pNode) { return 0; } if (mLastChild) { pNode->mNext = 0; pNode->mPrev = mLastChild; mLastChild->mNext = pNode; mLastChild = pNode; } else { mFirstChild = mLastChild = pNode; pNode->mPrev = 0; pNode->mNext = 0; } pNode->mParent = this; return pNode; }总结组合模式,主要用于将相同类型的对象统一的按照树形方式管理起来,便于实现"增、删、改、查"的行为。类似Xml、部门管理的场景比较适合。在使用过程中,客户端需要手动创建和释放,后面更新为自动创建回收就比较完美了。
命令模式命令模式是一种行为模式,用于将请求或频繁调用关系管理起来,封装成对象行为。意义在平常的设计中,对于不同模块之间调用关系,通常通过头文件直接调用接口即可。随着需求的增多,调用越来越多。最终维护起来会发现如下问题:代码裁剪时任务量大。由于调用过多,且调用关系散落各处,导致裁剪代码时,不敢轻易下手。各个组件关系复杂。在实际的编程中,经常会遇到一些“特殊”场景,需要特殊处理(组件之间调用),而这些代码又未规范起来。因此在新人维护代码时,会忽略这些特殊场景。更恐怖的是,遇到重复的需求,又加上重复的代码。代码变得“不纯粹”。命令模式可以解决以上问题。类图命令模式Invoker:执行实例,此对象完成命令申请和命令响应的整个流程。Command:命令对象基类,定义命令类的模板。LedCmd、MotorCmd:具体的命令类,其内部会持有Receiver的指针,用于将具体的命令实例和响应实例绑定。此类中,可以做一些对命令的管理等操作。Receiver:命令响应类,用于响应某个命令的执行动作。Client:客户端代码,如果规范起来的话,Client应只能访问对外释放的接口。在此指定命令与指定响应行为绑定。总结命令模式将模块之间的调用关系,分离为命令发起、命令处理和命令响应,从而完成代码间的解耦。命令对象与响应对象通过动态的注册的方式来实现,更容易完成响应者的替换。加入新的需求时,只需要增加新的对象即可,无需修改原先的业务代码,更加安全。结合之前分享的进程间通信,能够将这些命令管理起来,实现不同进程间的命令发起和响应。想想都挺好玩!(进程间通信文章见文末推荐)但是此种命令模式,客户端要创建和销毁很多实例,这对于使用者来说是麻烦的操作。优化代码已经完成,等后续总结一篇来介绍。可公众号获取优化的代码。源码#include <iostream> #include <string> usingnamespacestd; // 命令响应 class Receiver { public: explicit Receiver(string operation) { mOperation = operation; } void Action() { cout << mOperation << endl; } private: string mOperation; }; class Command { public: virtual ~Command() { } virtual void Execute() = 0; protected: Command() { } }; class LedCmd :public Command { public: void Execute() { if (NULL != mRcv) { this->mRcv->Action(); } } explicit LedCmd(Receiver *pRcv) { if (NULL != pRcv) { mRcv = pRcv; } else { mRcv = NULL; cout << "Error: Param failed!" << endl; } } private: Receiver *mRcv; }; class MotorCmd :public Command { public: void Execute() { if (NULL != mRcv) { this->mRcv->Action(); } } explicit MotorCmd(Receiver *pRcv) { if (NULL != pRcv) { mRcv = pRcv; } else { mRcv = NULL; cout << "Error: Param failed!" << endl; } } private: Receiver *mRcv; }; class Invoker { public: explicit Invoker(Command *pCmd) { if (NULL != pCmd) { mCmd = pCmd; } else { mCmd = NULL; cout << "Error: Param failed!" << endl; } } // 所有的命令响应动作都会经此接口触发, 可以在此处实现对命令的管理设计 void Invoke() { if (NULL != mCmd) { mCmd->Execute(); } else { cout << "Error: no action" << endl; } } private: Command *mCmd; }; int main(int argc, char *argv[]) { Receiver *theLedRcv = new Receiver("Led"); LedCmd *theLedCmd = new LedCmd(theLedRcv); Invoker *theLedManager = new Invoker(theLedCmd); theLedManager->Invoke(); Receiver *theMotorRcv = new Receiver("Motor"); LedCmd *theMotorCmd = new LedCmd(theMotorRcv); Invoker *theMotorManager = new Invoker(theMotorCmd); theMotorManager->Invoke(); delete theLedRcv; delete theLedCmd; delete theLedManager; delete theMotorRcv; delete theMotorCmd; delete theMotorManager; return 0; }$ ./a.out Led Motor
观察者模式观察者模式是一种行为设计模式,主要用于实现一种订阅机制,可在目标事件发生变化时告知所有观察此事件的对象,使观察者做出对应的动作。通常是通过调用各观察者所提供的方法来实现。UML类图观察者模式CObserver类,为观察者抽象类,为具体观察者定义标准接口:Update() 用于更新自身行为,由具体主题调用。GetName用于获取定义的字符,用于标识各个对象。CSubject类,为主题抽象类,主要为具体的主题类定义标准的接口。主要接口:Register(): 注册观察者Unregister(): 注销指定观察者Notify(): 通知所有观察者CRadio类,具体的主题类(CSubject子类)。例:无线电。实现注册/注销观察当前主题的接口Register/Unregister,供观察者调用。实现通知接口Notify,用于主题发布时,通知所有的观察者。**CInterphone、CPhone **类,具体的观察者类。例:对讲机、手机实现消息传递接口Update,供具体主题对象调用,用于通知主题内容。场景列举如上场景,现某处无线电在特定频道发出广播,所有关注此频道设备都会接收到此信号。具体主题为Radio,其内部需要在主题发布时,通知所有观察者class CRadio :public CSubject { public: CRadio() { mObserverList.clear(); } ~CRadio() { } int Register(CObserver *pObsr) { if (nullptr != pObsr) { mObserverList.push_back(pObsr); } else { OBSERVER_LOGE("Register failed!\n"); return-1; } return0; } int Unregister(CObserver *pObsr) { if (nullptr != pObsr) { mObserverList.remove(pObsr); } else { OBSERVER_LOGE("Unregister failed!\n"); return-1; } return0; } int Notify(void *pMsg) { list<CObserver *>::iterator it; SMsgTransfer *pGmsg = (SMsgTransfer *)pMsg; OBSERVER_LOG("Send radio: [0x%x] [%s]\n", pGmsg->type, pGmsg->buf); // loop: 向监听此事件的所有观察者发布主题 for (it = mObserverList.begin(); it != mObserverList.end(); it++) { (*it)->Update(pMsg); } return0; } private: list <CObserver *> mObserverList; };具体观察者为可接收到此无线电的设备:手机、对讲机/* 监听无线电的设备: 对讲机 */ class CInterphone :public CObserver { public: explicit CInterphone(string value) { mName = value; } ~CInterphone() { } string GetName() { return mName; } int Update(void *pMsg) { SMsgTransfer *msg = (SMsgTransfer *)pMsg; OBSERVER_LOG("%s receive: [0x%x] [%s]\n", mName.c_str(), msg->type, msg->buf); return0; } private: string mName; }; /* 监听无线电的设备: 手机 */ class CPhone :public CObserver { public: explicit CPhone(string value) { mName = value; } ~CPhone() { } string GetName() { return mName; } int Update(void *pMsg) { if (pMsg != NULL) { SMsgTransfer *msg = (SMsgTransfer *)pMsg; OBSERVER_LOG("%s receive: [0x%x] [%s]\n", mName.c_str(), msg->type, msg->buf); } else { OBSERVER_LOG("%s receive messages cannot be resolved!\n", mName.c_str()); } return0; } private: string mName; };客户端代码。使用Radio和设备形成应用场景CRadio theRadio; CPhone thePhoneUser1("Phone user1"); CPhone thePhoneUser2("Phone user2"); CInterphone theInterUser1("Interphone user1"); static int init() { // 注册监听无线电的设备,可放在观察者类初始化内部 theRadio.Register(&theInterUser1); theRadio.Register(&thePhoneUser1); theRadio.Register(&thePhoneUser2); return0; } int main(int argc, char *argv[]) { init(); // 发出SOS无线电 OBSERVER_LOG("------------------------------------------\n"); SMsgTransfer msg1 = {191519, "SOS"}; theRadio.Notify(&msg1); OBSERVER_LOG("------------------------------------------\n"); SMsgTransfer msg2 = {131313, "HELLO"}; theRadio.Notify(&msg2); return0; }输出:$ ./a.out ------------------------------------------ Send radio: [0x2ec1f] [SOS] Interphone user1 receive: [0x2ec1f] [SOS] Phone user1 receive: [0x2ec1f] [SOS] Phone user2 receive: [0x2ec1f] [SOS] ------------------------------------------ Send radio: [0x200f1] [HELLO] Interphone user1 receive: [0x200f1] [HELLO] Phone user1 receive: [0x200f1] [HELLO] Phone user2 receive: [0x200f1] [HELLO]总结观察者将耦合转移到抽象类之间,实现耦合倒转,具体的实现类无需关注此耦合,只需要完成分内的工作即可。主题无需关注观察者的数量及行为,观察者也不需了解主题具体的逻辑。满足开闭原则,当需要新增新的观察者时,只需要增加具体的观察者类即可,无需修改原有代码。一般情况下,观察者会被应用于不同进程之间。例如,在电视机的系统中,电视需要待机。此时当发生待机动作时,所有需要在待机时做出相应动作的进程都需要关注待机事件(例:APP需要保存当前数据)。此场景下,待机就为主题,其他进程为观察者。且需要增加一个通知类来维护广播的机制,此类需要具备跨进程通信和观察者机制。当系统中许多实例或组件需要关注同一个事件时,可采用观察者模式。
UML中类与类间的关系及实现在面向对象的程序设计中,类之间主要有六种关系:继承, 组合, 聚合,关联, 依赖,实现。C属于面向过程语言,本身的语法不支持这些关系的实现,但可通过结构体和指针来实现这些关系。C++/Java/Python等语言原生支持面向对象的程序设计,可通过其语法来完成这些关系。此次结合UML与C++来掌握这些关系的实现与运用。继承(Generalization)继承是面向对象三大特征之一,其主要作用是实现代码的重用。继承可用**"is a"**表示,例鸟(类B)是动物(类A)。从继承父类的数量又可细分为单继承和多继承。(个人认为代码设计中,多继承的使用会使代码变得难以定位,因此尽可能减少多继承的使用)类图例程// 单继承 class bird :public Animal { // 子类实现 }; // 多继承 class WangAnshi :public Poeat, public Politician { };组合(Composition)组合是将一个对象放到另一个对象里。是一种 "has a" 的关系,例如飞机(类b)有机翼(类a)。被组合的对象是一种强所属的关系,即两个对象具有相同的生命周期,一般情况下被组合的对象都不能脱离组合独自存在。例如飞机类和机翼类属于组合关系(假设一款机翼只能被一款飞机使用),在创建飞机对象时要同时或之后创建机翼对象;销毁飞机时,要同时或之前销毁机翼对象,两者都不能脱离组合独自存在。类图例程class Wing { }; class Airplane { private: Wing mWing[2]; };聚合(Aggregation)聚合是一种弱所属关系。不同与组合的是,聚合只是描述两者存在所属关系,且拥有者对象可控制被拥有者对象的生命周期;而组合不仅描述这种关系,而且被组合的对象生命周期相同。因此,组合是一种特殊的聚合(类似于正方形是特殊的长方形)。例如: 装饰一个房间,可以在房间摆放花,且任何时候都可以拿走花。此时房间与花就是一种聚合的关系。类图例程class Flower { }; class Room { public: void AddFlower(); void RemoveFlower(); private: Flower mFlower; };关联(Association)关联是一种弱关系。但并不为所属关系,两者是平等的类。关联仅表示一种类持有另一种类的关系。例如:老师和学生是两种独立平等的类。老师可以持有学生类,访问其分数。学生可以持有老师类,了解老师教授的课程。这种关系又称为双关联。类图例程class Teache { public: class Student *mStudent; }; class Student { public: class Teacher *mTeacher; };依赖(Dependency)一个对象的行为依赖另一种类。例如: 鸟的成长受水的影响,那么鸟与水就构成依赖关系。C++语法中,通过将一个类作为另一个类成员函数参数的形式来实现两者的依赖关系。类图例程class Water { }; class Bird { public: void GrowUp(Water &theWater); };实现(Realization)实现对应的是面向对象的"接口"。例如手机都有自己的logo,不同的手机logo不同,每种手机都要实现自己的logo。C++中,接口通过纯虚函数来实现,多态就是通过虚函数来实现的。类图例程class Phone { public: virtual string logoShow() = 0; }; class Mate40 :public Phone { public: string LogoShow(); };总结在初使用c++时,可能不会关注c++类之间的这些关系。但是当设计一个场景,一种模块的时候,就可以通过类之间的这些关系帮助自己更加清晰的了解每个模块的关系。以及自己设计的模块牵涉到哪些关联模块?是通过什么行为关联的?可能会发生什么动作?这些动作的发生会产生哪些场景?当把这些问题都搞清楚了,设计起来会大大降低漏掉的场景和隐藏的Bug。
桥接模式桥接是一种结构型设计模式,可将业务逻辑或一个大类拆分为不同的层次结构, 从而能独立地进行开发。简单的理解,就是将抽象部分与实现部分分离,实现解耦。场景实现Honor30Pro与Mate40手机的微信与王者荣耀APP。分析遇到此类场景时,通常我们会抽象出手机类,然后在不同的手机上实现客制化的APP(假设不同的手机品牌安装同一款APP会有一些参数差异)。如果将手机抽象,可以得到如下类图:桥接模式如上设计是将各个品牌手机相同部分抽象出来,差异性的部分可在具体的子类中实现。因此也就能够实现具备王者荣耀和微信的不同品牌手机(Honor、Mate40)。但是上述设计又暴露出如下问题:每增加一款手机,就要重新实现一个手机子类以及适应当前手机的各个APP。每个手机与其平台上的APP高度耦合。例如,当Hono30Pro类发生修改时,其子类王者荣耀APP以及微信APP都需要随之修改。这种设计还会导致APP与手机绑定,从而无法实现从某个手机卸载指定的APP。例:在Mate40手机卸载王者荣耀。将App抽象将APP抽象时,得到可用于Mate40与Honor30 Pro的微信与王者荣耀。APP抽象此种设计与第一种设计存在相同的问题。将手机与APP分别抽象当回头再分析场景时,发现场景存在两个实例,手机和APP,且为“has a”的关系,即聚合。那么可将手机与APP分别抽象,实现聚合的关系,子类无需关心两者间的关系。APP和手机抽象如上设计,仅CPhone类与CAPP类有耦合关系。在实际的实现中,CPhone与CAPP为依赖关系,具体CPhone子类与CAPP为依赖和聚合关系。将原先高度耦合的关系,转换为CPhone子类与CAPP抽象类耦合。如此设计有以下优点:完成CPhone子类与CApp子类的解耦。两者都可以独立修改,互不干扰。灵活性更强。更容易扩展。当需要增加新的手机或APP时,能够在不修改原有的代码基础上完成。代码#include <iostream> #include <string> #include <vector> usingnamespacestd; class CApp { public: CApp() { } virtual ~CApp() { } virtual string GetName() = 0; virtual void ShowLogo() = 0; virtual void RunPlatform(string platform) = 0; }; class CHonorOfKings :public CApp { public: CHonorOfKings(string logo) { mLogo = logo; mName = "HonorOfKings"; } ~CHonorOfKings() { } string GetName() { return mName; } void ShowLogo() { cout << mLogo << " (" << mPlatform << ")" << endl; } void RunPlatform(string platform) { mPlatform = platform; } private: string mName; string mLogo; string mPlatform; }; class CWeChat :public CApp { public: CWeChat(string logo) { mLogo = logo; mName = "WeChat"; } ~CWeChat() { } string GetName() { return mName; } void ShowLogo() { cout << mLogo << " (" << mPlatform << ")" << endl; } void RunPlatform(string platform) { mPlatform = platform; } private: string mName; string mLogo; string mPlatform; }; class CPhone { public: virtual ~CPhone() { } virtual void InstallApp(CApp *pApp) = 0; virtual void Uninstall(CApp *pApp) = 0; virtual void EnterApp(CApp *pApp) = 0; virtual void ShowAppList() = 0; }; class CMate40:public CPhone { public: explicit CMate40(string name) { mName = name; } ~CMate40() { } void InstallApp(CApp *pApp) { pApp->RunPlatform(mName); mAppVec.push_back(pApp); } void Uninstall(CApp *pApp) { vector<CApp *>::iterator it; for (it = mAppVec.begin(); it != mAppVec.end(); ) { if ((*it)->GetName() == pApp->GetName()) { cout << "Uninstall " << (*it)->GetName() << " Success!" << endl; /* void removing the last element, coredump */ it = mAppVec.erase(it); } else { it++; } } } void EnterApp(CApp *pApp) { pApp->ShowLogo(); } void ShowAppList() { vector<CApp *>::iterator it; cout << "App List: "; for (it = mAppVec.begin(); it != mAppVec.end(); ) { cout << (*it)->GetName() << " "; it++; } cout << endl; } private: string mName; vector <CApp*> mAppVec; }; class CHonor30Pro :public CPhone { public: explicit CHonor30Pro(string name) { mName = name; } ~CHonor30Pro() { } void InstallApp(CApp *pApp) { pApp->RunPlatform(mName); mAppVec.push_back(pApp); } void Uninstall(CApp *pApp) { vector<CApp *>::iterator it; for (it = mAppVec.begin(); it != mAppVec.end(); ) { if ((*it)->GetName() == pApp->GetName()) { cout << "Uninstall " << (*it)->GetName() << " Success!" << endl; /* void removing the last element, coredump */ it = mAppVec.erase(it); } else { it++; } } } void EnterApp(CApp *pApp) { pApp->ShowLogo(); } void ShowAppList() { vector<CApp *>::iterator it; cout << "App List: "; for (it = mAppVec.begin(); it != mAppVec.end(); ) { cout << (*it)->GetName() << " "; it++; } cout << endl; } private: string mName; vector <CApp*> mAppVec; }; int main(int argc, char *argv[]) { CHonorOfKings theHonorOfKings("Timi"); CWeChat theWeChat("Earth"); // 定制 Honor 30 Pro 上的王者荣耀和微信 CHonor30Pro thePhone("Honor 30 Pro"); cout << "--- Honor 30 Pro ---" << endl; thePhone.InstallApp(&theHonorOfKings); thePhone.EnterApp(&theHonorOfKings); thePhone.InstallApp(&theWeChat); thePhone.EnterApp(&theWeChat); thePhone.ShowAppList(); thePhone.Uninstall(&theWeChat); thePhone.ShowAppList(); cout << endl; // 定制 Mate40 上的王者荣耀和微信 CMate40 theMate40("Mate40"); cout << "--- Mate40 ---" << endl; theMate40.InstallApp(&theHonorOfKings); theMate40.EnterApp(&theHonorOfKings); theMate40.InstallApp(&theWeChat); theMate40.EnterApp(&theWeChat); theMate40.ShowAppList(); theMate40.Uninstall(&theWeChat); theMate40.ShowAppList(); cout << endl; return0; }总结桥接模式主要将抽象与实现分离。完成相同的部分耦合,差异化部分解耦。使代码灵活性更强,达到类实现部分的修改不会影响到其他代码改动的效果。客户端代码仅能够与高层抽象部分交互,不会接触到具体实现的详细细节。履行开闭原则。新增抽象部分与实现部分,两者互不影响。履行单一原则。抽象部分专注于处理高层逻辑,实现部分处理差异化细节。
状态模式允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。--百度百科简单地说,状态模式就是状态机设计。其主要用于同一个请求,不同条件下执行不同的功能,作用等同于if...else。意义在实际编码工作中,经常会遇到一些特殊的场景与普通场景产生的结果不同,逼迫我们不得不使用if来规避。随着时间的推移,这些if使用的越来越多,导致代码晦涩难懂,变成“一坨”,不易维护。此时,我们就应当分析这些特殊的场景,是在哪些特殊条件下产生,能否与普通场景分离,在不影响主干流程的基础上将此场景增加进去。在搞清楚这些条件后,就可以引进状态机进行代码的重构。场景电梯在运行过程中,随时都有上电梯、下电梯和等待电梯的需求。这些需求,在电梯空闲、上行或者下行时又会产生不同的动作。例如:假设电梯处于2楼且空闲状态,遇到1楼需要乘坐电梯。此时电梯应该下行至1楼。假设乘客在2楼上电梯,并在电梯内按下5楼,此时遇到1楼乘坐电梯。此时电梯应先上行至5楼,再下行至一楼。同样的需求,由于电梯运行的状态不同,导致产生不同的动作。那么,如果用代码来实现电梯的运行?分析按照往常的编码习惯,通常会写一个电梯运行线程,在线程增加各种if用于处理不同的场景。如此设计会存在一些隐患:所有场景都混在一起,导致思路混乱,容易遗漏特殊场景。不能及时发现部分场景产生冲突,导致预期之外的行为Bug。if大量使用,导致很难理清代码运行流程,难以维护。此时,引用状态机就能够很清晰的描述这些场景:先理清楚电梯的运行状态,无非分为:上行、下行、空闲和故障。再搞清楚电梯会遇到哪些需求,主要是:当前楼层上预约乘坐电梯、当前楼层下预约乘坐电梯和下电梯。最后整理出不同状态下遇到这些需求的处理方式以及电梯状态的切换条件,就能够构建出所有场景。状态图状态模式① 开机运行② 在当前楼层之上楼层存在需求③ 当前不存在乘坐需求④ 当前楼层之上不存在需求,楼层之下存在需求⑤ 当前楼层之下不存在需求,楼层之上存在需求⑥ 当前楼层下存在需求⑦ 不存在乘坐需求⑧ 电梯主动切换停止状态⑨ 电梯主动切换空闲状态编码由于代码量较多,本篇仅贴出部分核心的代码片段:状态表CElevatorSrv::mStateTable[] = { { LEV1_ANY, LEV2_ANY, SIG_ID_POWER_ON, &CElevatorSrv::MsgRespondInit}, { LEV1_DOOR_OPEN, LEV2_IDLE, SIG_ID_POWER_OFF, &CElevatorSrv::MsgRespondShutdown}, { LEV1_DOOR_CLOSE, LEV2_IDLE, SIG_ID_TAKE_UP_ORDER, &CElevatorSrv::MsgRespondOrderUpIdle}, { LEV1_ANY, LEV2_ANY, SIG_ID_TAKE_UP_ORDER, &CElevatorSrv::MsgRespondOrderUp}, { LEV1_DOOR_CLOSE, LEV2_IDLE, SIG_ID_TAKE_DOWN_ORDER, &CElevatorSrv::MsgRespondOrderDownIdle}, { LEV1_ANY, LEV2_ANY, SIG_ID_TAKE_DOWN_ORDER, &CElevatorSrv::MsgRespondOrderDown}, { LEV1_ANY, LEV2_ANY, SIG_ID_ARRIVE_FLOOR, &CElevatorSrv::MsgRespondArriveFloor}, { LEV1_ANY, LEV2_IDLE, SIG_ID_EXIT, &CElevatorSrv::MsgRespondExit}, { LEV1_ANY, LEV2_ANY, SIG_ID_ANY, &CElevatorSrv::MsgRespondIgnore} };注:由于在实际场景中,电梯仅允许关门时才可运行。因此这里将开门、关门作为一级状态,电梯运行状态作为二级状态。消息处理函数void CElevatorSrv::ProcessMsg(SMsgPacket *pMsg) { if (!IsStart()) { ELEVATORSRV_LOGE("Elevator not start!\n"); return; } if (pMsg == nullptr) { ELEVATORSRV_LOGE("pMsg is nullptr!\n"); return; } int index = 0; EElevatorDoorState curLev1State = GetLev1State(); EElevatorRunState curLev2State = GetLev2State(); EMsgType msgId = pMsg->type; ELEVATORSRV_LOGD("Get Msg: 0x%x\n", msgId); // loop: 遍历状态表,进入与状态匹配的入口 do { if ( ( (mStateTable[index].lev1State == curLev1State) || (mStateTable[index].lev1State == LEV1_ANY) ) && ( (mStateTable[index].lev2State == curLev2State) || (mStateTable[index].lev2State == LEV2_ANY) ) && ( (mStateTable[index].msgId == msgId) || (mStateTable[index].msgId == SIG_ID_ANY) ) ) { (this->*(mStateTable[index].callback))(pMsg); break; } index++; } while (1); }通过查表的方式,进入匹配当前状态的响应函数。总结状态模式的实现方法难度不大,主要是编程的思想的上升。将状态与需求绑定,不仅能够实现统一需求的不同响应方式,还能实现某些状态下不响应指定需求。场景清晰,思路明确。当需要增加状态或者需求时,只需在表内增加即可,而无需修改已有的逻辑,符合开闭原则。状态模式的使用,可以使关注点仅放在当前状态遇到需求产生的完整流程。无需考虑其他状态的影响。将需求与响应解耦,还能够实现通信管理。例如,不同进程间的需求响应,可以将两者通信设计为不涉及业务的跨进程通信,从而实现通信代码的可复用。总的来说,状态模式是一种非常实用的设计模式。不仅是从代码上还有设计思路上,减轻设计师对复杂业务的整理工作。相当的完美!
中介者模式❝用一个中介对象来封装一系列对象的交互,从而把一批原来可能是交互关系复杂的对象转换成一组松散耦合的中间对象,以有利于维护和修改。中介者模式是将**「多对多」的交互关系转化为「一对多」**。软件中设计一个中介对象专门管理交互逻辑,如此一来便能够将各个对象间错综复杂的耦合统一到与中介对象耦合。适用中介者模式的场景当多个组件或对象互相依赖,动一发而牵全身。该模式能够使各个组件相互独立,互不耦合,从而保证各个组件的修改互不牵连。当某些组件因为依赖其他组件,而无法复用时。采用中介者将组件间的依赖仅面向中介者。当需要在不同场景复用基本行为,导致被迫产生大量组件子类。由于所有组件间关系都被包含在中介者中, 因此你无需修改组件就能方便地新建中介者类以定义新的组件合作方式典型案例典型的租房案例。租房中介整合多个房东的房源,用户只需要从中介这里找到适合自己的房源即可。租房中介便将房东与用户的交互管理起来,多个房东和用户都只需与租房中介交互。机场塔台场景。飞机在降落前需要提前了解降落方位,因此需要了解其他飞机的降落位置,从而获取到空闲的降落位置。每个飞机之间都要建立联系。从图上看,飞机之间关系相当复杂。降落飞机需要向所有的飞机发送请求信息并等待应答,效率低下。为提高效率,引入塔台角色。塔台事先与其他飞机交互保存各个飞机的信息,当有飞机发送降落请求时,塔台根据保存的信息应答降落请求即可。如此一来,大大提升了交互效率。中介者模式UML类图这里以塔台案例为案例绘制UML类图。CToWerBase: 塔台抽象类。CChengduTower: 具体的塔台类,扮演中介者角色,与各个飞机交互。CAirPlane: 飞机抽象类。CAirBusA319、CBoeing737、CComacC919:具体的飞机类,只与塔台对象发生交互。源码实现本源码只实现了塔台与飞机间的交互框架,具体的飞机停靠业务逻辑没有体现。「客户端代码」int main(int argc, char *argv[]) { CChengduTowerMediator *theChengduTower = CChengduTowerMediator::GetInstance(); CAirBusA319 *theAirBus319 = new CAirBusA319(); theAirBus319->SetMediator(theChengduTower); CBoeing737 *theBoeing737 = new CBoeing737(); theBoeing737->SetMediator(theChengduTower); CComacC919 *theCComacC919 = new CComacC919(); theCComacC919->SetMediator(theChengduTower); // 塔台向所有飞机广播,通报姓名 //MAIN_LOGI("--> Tower Radio: notify name\n"); theChengduTower->NotifyObserverAll((char *)"Please notify your name"); MAIN_LOG("\n"); // AirBus319应答塔台 //MAIN_LOGI("--> AirBus319 Notify\n"); theAirBus319->Notify((char *)"This is AirBus319"); MAIN_LOG("\n"); // theBoeing737应答塔台 //MAIN_LOGI("--> Boeing737 Notify\n"); theBoeing737->Notify((char *)"This is Boeing737"); MAIN_LOG("\n"); // CComacC919应答塔台 //MAIN_LOGI("--> CComacC919 Notify\n"); theCComacC919->Notify((char *)"This is CComacC919"); MAIN_LOG("\n"); delete theAirBus319; delete theBoeing737; delete theCComacC919; return 0; }「执行效果」$ ./exe [CAirBusA319] Recv: Please notify your name [CBoeing737] Recv: Please notify your name [CComacC919] Recv: Please notify your name Tower Recv: [0] This is AirBus319 [CAirBusA319] Recv: Ack Tower Recv: [1] This is Boeing737 [CBoeing737] Recv: Ack Tower Recv: [2] This is CComacC919 [CComacC919] Recv: Ack 「塔台接口」class CChengduTowerMediator : public CTowerMediatorBase { public: CChengduTowerMediator(); ~CChengduTowerMediator(); static CChengduTowerMediator* GetInstance(); // 注册飞机实例 int Register(CAirplaneBase* airplane); // 注销指定飞机ID int UnRegister(int RegisterId); // 向指定飞机发送消息 int SendMsg(int RegisterId, void *msg); // 向所有注册飞机发送广播 int NotifyObserverAll(void *msg); // 接收飞机消息 int Receive(int id, void *msg); private: // 飞机注册表 std::map<int, CAirplaneBase*> mAirplaneHashTable; };「飞机接口」class CAirBusA319 : public CAirplaneBase { public: CAirBusA319(); ~CAirBusA319(); void SetMediator(CTowerMediatorBase *meditor); int Notify(void *msg); int Receive(void *msg); private: int mIdForMediator; CTowerMediatorBase *pmMeditor; };总结中介模式的实现不是太复杂,主要引进一个“中介”角色,将各个组件的交互全部转移到与“中介”交互中来。设计思想,便是将**「多对多」杂乱无章的交互关系,梳理为「一对多」**有序的交互。中介者模式的思想主要为尽量减少各个组件之间的耦合关系,将复杂的**「多对多」耦合关系转化为中介者对各个组件「一对多」**的耦合关系。中介者模式解除了各个组件之间的耦合,使其相对独立。自身改动不会影响其他组件代码的修改,同时顾忌其他组件的变化带来的影响。满足单一原则,更易维护。在软件的设计中,大都是多个设计模式搭配使用的,本篇实现不仅包含中介者模式还有观察者模式的体现。因此在面对具体的场景时,只根据其组件特性选择对应的一种或多种设计模式即可。
策略模式策略模式定义多种处理同一种场景的不同算法,这些算法可在不影响用户的情况下互相替换。场景应用场景某会员制商场有两种级别会员:银卡会员、黄金会员,分别享有9折、8折购物优惠。同时不同的会员用户在生日当天购物,会赠送不同的生日礼物。分析场景比较简单,需要解决的问题是区分不同类型的顾客享有不同的权益(折扣和生日礼物)。按照平常的编码习惯,通常会在需要区分用户等级的业务上加上if判断,实现不同等级顾客应享有的不同权益。这种方案能快速的解决实际问题,但是随着业务需要,商场又要引进更高逼格的会员类型,譬如白金会员、钻石会员等。此时,需要在散落在各处的业务代码上加上if补丁。这种做法会引来如下问题:业务代码散落各处,容易出现漏加if的情况,又难以验证。时间推移,if会越来越多,导致分支变多,代码走向模糊,影响代码维护。解决方案引入策略模式,将用户等级抽象,分别定义出用户所有的行为,不同等级的用户各自实现该等级应享有的权益。类图策略模式CShop: 商场类。实现不同等级顾客的折扣结算、生日礼物等。持有CConsumer指针,根据需要指向具体的顾客实例(CCommonUser、CSilverUser、CGoldUser)。CConsume: 顾客抽象类。定义顾客所有的行为接口。CCommonUser、CSilverUser、CGoldUser: 具体顾客类。不同等级顾客实现有差异部分的接口。效果执行效果$ ./exe --------------------------------- All Cost : 1000.00. User Type : Common User. Discount : 1.00. Actual Payment: 1000.00. --------------------------------- All Cost : 1000.00. User Type : Silver User. Discount : 0.90. Actual Payment: 900.00. --------------------------------- All Cost : 1000.00. User Type : Gold User. Discount : 0.80. Actual Payment: 800.00.客户端实现int main(int argc, char *argv[]) { CShop theShop; float cost = 1000.0; // 普通用户 MAIN_LOG("\n---------------------------------\n"); MAIN_LOG(" All Cost : %0.2f. \n" " User Type : %s. \n" " Discount : %0.2f. \n" " Actual Payment: %0.2f. \n", cost, theShop.GetUserDesc().c_str(), theShop.GetCurrentDiscountRate(), theShop.GetRealPrice(cost)); // 切换白银会员 MAIN_LOG("\n---------------------------------\n"); theShop.SetConsumer(COSUMER_SILVER); MAIN_LOG(" All Cost : %0.2f. \n" " User Type : %s. \n" " Discount : %0.2f. \n" " Actual Payment: %0.2f. \n", cost, theShop.GetUserDesc().c_str(), theShop.GetCurrentDiscountRate(), theShop.GetRealPrice(cost)); // 切换黄金会员 MAIN_LOG("\n---------------------------------\n"); theShop.SetConsumer(COSUMER_GOLD); MAIN_LOG(" All Cost : %0.2f. \n" " User Type : %s. \n" " Discount : %0.2f. \n" " Actual Payment: %0.2f. \n", cost, theShop.GetUserDesc().c_str(), theShop.GetCurrentDiscountRate(), theShop.GetRealPrice(cost)); return0; }总结策略模式 的实现原理比较简单,主要是改变持有指针的指向,即可实现不同方案的切换。更改为外部条件输入匹配对应的实例,便可以做到客户端代码无需改变自动切换不同方案的效果。满足开闭原则。当需增加策略时,只需要派生一个新的策略即可,而无需修改现有代码。相对以前的做法,更加安全快捷。在代码运行时,也可以动态切换策略。策略模式 与 命令模式 有点相像。两者的实现方式类似,应对的场景不同。策略模式针对的是同一个行为不同的算法实现;命令模式 针对是一个命令对应一个解决的方法。篇幅有限,本文只列举部分主要代码。全部源码可在公众号后台输入标题获取。源码商店类接口class CShop { public: CShop(); ~CShop(); std::string GetUserDesc() { return mConsumer->mUserDesc; } float GetRealPrice(float price); int BirthdayPresent(); int SetConsumer(EConsumerType type); void SetCurrentDiscountRate(float rate); float GetCurrentDiscountRate(); private: CConsumer* mConsumer; };更新顾客类型int CShop::SetConsumer(EConsumerType type) { switch (type) { case COSUMER_COMMON: mConsumer = CCommonUser::GetInstance(); break; case COSUMER_SILVER: mConsumer = CSilverUser::GetInstance(); break; case COSUMER_GOLD: mConsumer = CGoldUser::GetInstance(); break; default: break; } if (NULL == mConsumer) { return-1; } return0; }顾客类抽象接口class CConsumer { public: float mDiscountRate; std::string mUserDesc; CConsumer() : mDiscountRate(1.0) { } virtual ~CConsumer() { } void SetDiscountRate(float rate) { mDiscountRate = rate; } float GetDiscountRate() { return mDiscountRate; } float GetRealPrice(float price) { return mDiscountRate * price; } virtual int GetBirthdayPresent() = 0; };具体顾客类:黄金会员class CGoldUser :public CConsumer { public: CGoldUser(); ~CGoldUser(); static CGoldUser* GetInstance(); int GetBirthdayPresent(); };客户端接口int main(int argc, char *argv[]) { CShop theShop; float cost = 1000.0; // 普通用户 MAIN_LOG("\n---------------------------------\n"); MAIN_LOG(" All Cost : %0.2f. \n" " User Type : %s. \n" " Discount : %0.2f. \n" " Actual Payment: %0.2f. \n", cost, theShop.GetUserDesc().c_str(), theShop.GetCurrentDiscountRate(), theShop.GetRealPrice(cost)); // 切换白银会员 MAIN_LOG("\n---------------------------------\n"); theShop.SetConsumer(COSUMER_SILVER); MAIN_LOG(" All Cost : %0.2f. \n" " User Type : %s. \n" " Discount : %0.2f. \n" " Actual Payment: %0.2f. \n", cost, theShop.GetUserDesc().c_str(), theShop.GetCurrentDiscountRate(), theShop.GetRealPrice(cost)); // 切换黄金会员 MAIN_LOG("\n---------------------------------\n"); theShop.SetConsumer(COSUMER_GOLD); MAIN_LOG(" All Cost : %0.2f. \n" " User Type : %s. \n" " Discount : %0.2f. \n" " Actual Payment: %0.2f. \n", cost, theShop.GetUserDesc().c_str(), theShop.GetCurrentDiscountRate(), theShop.GetRealPrice(cost)); return0; }