设计模式学习笔记
-
设计模式学习笔记
1. 设计模式简介
1. 设计模式是什么
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。简单来说,设计模式就是被人总结出来的一些写好代码的方法或者套路。
2. 设计模式为了什么
使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。毕竟是套路得人心嘛。
3. 设计模式的分类
- 创建型模式
- 结构型模式
- 行为型模式
2. 设计模式中的六大原则
1. 单一职责原则
一个类只负责一件事,一个类应该仅有一个变化的原因。如果有多个动机来改变一个类,说明这个类所承担的职责过多。
2. 开放封闭原则
对扩展开放,对更改封闭。个人认为是面向对象最重要的一个原则,添加代码相比于修改已有的代码更为安全,也更能避免程序成为屎山。
3. 依赖倒置原则
细节依赖于抽象,而不是抽象依赖于细节,简单来说,不要面对实现编程,要面对接口编程,要装电脑,而不是装收音机。
4. 迪米特原则
尽量减少一个对象对另一个对象的了解,这样能保证在一个类修改时,减少对于其他类的影响。保持类和类的松耦合关系。
5. 里氏替换原则
能够使用父类的地方应该可以安全的替换成子类。该原则在我目前看来是开放封闭原则的一种实现方式,该原则要求我们尽量将父类设置成抽象类或者接口,用子类来重写所需要的方法,在运行时再确定需要的子类类型,用子类实例来替换父类实例。(kenzo代码中比如 IEventData 类就是相似这种方式的实现)
6. 接口隔离原则
建立单一接口而不是建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
3. UML类图
UML图一般用于表示程序中类之间的关系,下面给出UML图的一个样例:
该图片截图自《大话设计模式》
类的定义和表示
该矩形框表示一个类(Class),分为三层
- 第一层为类名,抽象类可用 斜体 表示
- 第二层为该类的一些属性和一些字段
- 第三层为该类的方法
其中,public用 “+” 表示,private用 “-” 表示,protected 用 “#” 表示。
接口的定义和表示
接口和类的定义类似,仅仅在类名的上面增加了一个
interface
标识。继承关系
继承关系用实线加三角形来表示,三角形指向的是父类,父类中实现的方法和属性在子类中不用重新表示。
接口实现关系
接口实现是用虚线加三角形来表示的,三角形指向的是被实现的接口。接口中的方法由于一般需要重写,所以需要在子类中表示。
关联关系
当一个类需要知道另一个类时,可以用关联方式来表示这种关系。
关联关系时用一个带箭头的实现来表示的,箭头指向的为被关联的类。
聚合关系
聚合关系表示的是一种弱拥有关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分。
聚合关系是由一个空心菱形和一条带箭头的实线来表示的。箭头指向的为被聚合的类。
组合(合成)关系
合成(组合)关系是一种强拥有关系,体现了严格的部分和整体的关系,部分和整体的生命周期相同。
组合关系是由一个实心菱形和一条带箭头的实线来表示的。箭头指向的为被组合的类。
同时在连线边的1,2为基数,表示这端的类可以有几个实例,如果一个类可以有无数个实例,则可以用n表示。关联关系,聚合关系也可以有基数。
依赖关系
依赖关系体现一个类需要有另一些类的存在。
依赖关系用虚线表示,箭头指向被依赖的类。
设计模式的三大分类
创建型模式
单例模式、建造者模式、工厂模式、原型模式。
结构型模式
适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
行为型模式
模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。
具体代码分析
工厂模式
工厂模式的本质应当就是对获取对象过程的抽象。同时,在该过程中可以在工厂类进行一些判断,筛选等,返回对应的所希望的类的实例化。
工厂模式可以分为三种:简单工厂模式,抽象工厂模式,工厂方法模式。
主要有以下三种类或者对象:
- 工厂类
- 抽象产品类
- 具体产品类
三种不同的工厂模式的区别主要在于工厂类的不同。
简单工厂就一个工厂类,工厂方法模式中,会有一个抽象工厂,各种子类继承它,实现子类的工厂,相当于把工厂职责细分。抽象工厂模式有点相当于简单工厂模式和工厂方法模式的结合。
简单工厂模式
下面就是一个简单工厂模式的使用实例:
//filepath: \...\Include\Framework\ComponentRepository.h //部分节选 //组件工厂,单例类:从DLL中加载组件 class FWK_EXP ComponentRepository: public Singleton { public: DECLARE_CU_SINGLETON(ComponentRepository) protected: ComponentRepository(void); ~ComponentRepository(void); public: void Load(); //根据缓存注册数据加载组件 void Unload(); //卸载所有组件 void Register(); //注册组件 void Unregister(); //启动组件运行 void Start(); void Stop(); void LoadComponent(CString fileName,CString domain,DocFormat fmt); //根据指定配置文件 加载组件 void RegComponent(CString modName,CString className,CString comName); //组件组件 void RegComponent(CString modName,CString comName); IComponent* GetComponent(CString name); //获取指定组件接口 void GetComponents(std::vector<IComponent*>& buf); //获取所有组件 private: // 字符串辅助函数 CString GenDLLName(CString modName); //获取组件 DLL名称,创建函数名称,组件实现类名称 CString GenCreateFuncName(CString className); CString GenClassName(CString comName); // 读取并加载组件名 bool ValidateComName(CString name); //名称唯一性验证 void LoadFromINIFile(CString fileName,CString domain); //INI配置文件加载组件实现 void LoadFromXMLFile(CString fileName,CString domain); //XML配置文件加载组件,赞未实现 void RegFullComponent(CString modName,CString className,CString comName); //注册组件全名实现 private: std::vector<ModuleData> m_regBuf; //组件注册数据缓存 std::vector<IComponent*> m_ComBuf; //组件缓存 std::vector<HMODULE> m_modrefBuf; //DLL模块句柄缓存 };
大致可以知道该工厂模式根据传入的字符串,来返回不同组件的指针。
工厂方法模式
情景:两种人:当代大学生和社区志愿者,均要学习雷锋,帮助老人。(情景来源于 《大话设计模式》)
UML图如下:
代码如下:
//Factory.h /** * \brief 工厂抽象类 */ class IFactory { public: virtual ~IFactory() = default; virtual LeiFeng* createLeiFeng() = 0; }; class VolunteerFactory final : public IFactory { public: LeiFeng* createLeiFeng() override; }; class StudentFactory final : public IFactory { public: LeiFeng* createLeiFeng() override; }; //Factory.cpp LeiFeng* VolunteerFactory::createLeiFeng() { LeiFeng* p_v = new Volunteer(); return p_v; } LeiFeng* StudentFactory::createLeiFeng() { LeiFeng* p_s = new Student(); return p_s; }
//LeiFeng.h class LeiFeng { public: virtual ~LeiFeng() = default; virtual void helpDo1() = 0; virtual void helpDo2() = 0; }; class Student final : public LeiFeng { public: virtual void helpDo1() override; virtual void helpDo2() override; }; class Volunteer final : public LeiFeng { public: virtual void helpDo1() override; virtual void helpDo2() override; }; //LeiFeng.cpp void Student::helpDo1() { std::cout << "student help do thing 1 " << std::endl; } void Student::helpDo2() { std::cout << "student help do thing 2 " << std::endl; } void Volunteer::helpDo1() { std::cout << "volunteer help do thing 1 " << std::endl; } void Volunteer::helpDo2() { std::cout << "volunteer help do thing 2 " << std::endl; }
//main.cpp int main() { IFactory* factory1 = new StudentFactory(); LeiFeng* student1 = factory1->createLeiFeng(); LeiFeng* student2 = factory1->createLeiFeng(); IFactory* factory2 = new VolunteerFactory(); LeiFeng* volunteer1 = factory2->createLeiFeng(); LeiFeng* volunteer2 = factory2->createLeiFeng(); student1->helpDo1(); student2->helpDo2(); volunteer1->helpDo1(); volunteer2->helpDo2(); return EXIT_SUCCESS; }
工厂方法模式和简单工厂模式的一些比较
工厂方法模式是简单工厂模式的进一步抽象。
在简单工厂中,可以把生成哪一种类的对象的逻辑放在工厂中,但在工厂方法模式中,由于此时是由子工厂类决定返回的类型,实例化哪个类型的子类成为了客户端的需求。
工厂方法模式克服了简单工厂模式违背开放—封闭原则的缺点,避免了修改工厂类的代码。
但工厂方法模式并没有解决掉 if-else 或者 switch 的相关逻辑代码,只是将简单工厂中这类逻辑代码转移到了客户端进行实现。
装饰模式
装饰模式介绍
情景:对一个Person类进行装饰(情景来源于《大话设计模式》)
也可以将Person类作为抽象类,继承出子类比如Man、Woman等等,在此就未进行该方面的继承,直接使用Person类进行初始化和进行装饰。
C++代码如下:
//Person类的声明 class Person { public: Person(); Person(string strName); virtual void showDecorate(); private: string m_Name; }; //Person类中方法的实现 Person::Person() { } Person::Person(string strName) { m_Name = strName; } void Person::showDecorate() { cout << m_Name << "的装饰:" << endl; }
//Decorator抽象类的声明(此抽象类是意义上的抽象,不是实际语法上的抽象类) class Decorator : public Person { public: Decorator(Person* person); virtual void showDecorate(); private: Person* mp_person; }; //类中一些方法的实现 Decorator::Decorator(Person* person):mp_person(person) { } void Decorator::showDecorate() { mp_person->showDecorate(); } //DecoratorA类声明 class DecoratorA : public Decorator { public: DecoratorA(Person* person); void showDecorate(); private: void AddDecorate(); }; //DecoratorA类方法的实现 DecoratorA::DecoratorA(Person* person):Decorator(person) { } void DecoratorA::showDecorate() { Decorator::showDecorate(); AddDecorate(); } void DecoratorA::AddDecorate() { cout << "A "; } //DecoratorB类声明 class DecoratorB : public Decorator { public: DecoratorB(Person* person); void showDecorate(); private: void AddDecorate(); }; //DecoratorB类方法的实现 DecoratorB::DecoratorB(Person* person):Decorator(person) { } void DecoratorB::showDecorate() { Decorator::showDecorate(); AddDecorate(); } void DecoratorB::AddDecorate() { cout << "B "; }
//main函数 int main() { string personName = "qwer"; Person* person = new Person(personName); Person* pda = new DecoratorA(person); Person* pdb = new DecoratorB(pda); pdb->showDecorate(); return 0; }
装饰模式总结
该模式利用了C++中虚函数的性质,子类重写虚函数,修改虚函数表指针。
装饰模式是为已有功能动态地添加更多功能的一种方式。这种模式可以将类中的一些相关的装饰功能移除,利用子类的方式动态添加,使最初的类更简单,同时还可以除去最初类中一些重复的装饰逻辑。
装饰模式提供了更加灵活的向对象添加职责的方式。可以用添加和分离的方法,用装饰在运行时刻增加和删除职责。装饰模式提供了一种“即用即付”的方法来添加职责。它并不试图在一个复杂的可定制的类中支持所有可预见的特征,相反,你可以**定义一个简单的类并给出装饰的方法接口,此后就可以用装饰类不断重写该方法接口给它逐渐地添加功能。**便可以从简单的部件组合出复杂的功能。
装饰模式重点在装饰,对核心功能的装饰作用;将继承中对子类的扩展转化为功能类的组合,从而将需要对子类的扩展转嫁给用户去进行调用组合,用户如何组合由用户去决定。
观察者模式
观察者模式介绍
此处就不贴《大话设计模式》里的例子了。
//filepath:\...\Include\Framework\EventArea.h //事件域基类型,定义事件分发,处理相关机制 class FWK_EXP EventArea:public IEventArea { public: EventArea(void); ~EventArea(void); //注册,解除 事件处理器 void RegHandler(const ID& id,IEventHandler* pHandler); void UnregHandler(const ID& id,IEventHandler* pHandler); protected: //事件分发 void FindHandlers(const ID& id,std::vector<IEventHandler*>& buf); // 返回指定ID的所有handler void DispatchEvent(const ID& id,IEventData* pData,bool bwholeLock=true); // 事件调度 //清理 void Clear(); protected: AutoCreateLock<CriticalSectionLock> m_HandleLock; // 锁 std::map<ID,std::vector<IEventHandler*>* > m_regBuf; // handler与ID的对应关系 };
上述可以看作是抽象观察者(尽管不是抽象类),各种窗口类继承该类型,都可以调用RegHandler() 和 UnregHandler() 函数。
在程序中,它观察的对象是对应的Model类。
-
最后一段的代码是项目里相关代码,项目中构建以Model层为center的事件域,view层通过注册来观察model层,从而对事件进行响应。