设计模式是一套代码设计「经验的总结」。项目中「合理的」运用设计模式可以「巧妙的解决很多问题」。
为什么要提倡“Design Pattern呢?根本原因是为了代码复用,增加可维护性。那么怎么才能实现代码复用呢?
模式的定义:模式是一种问题的解决思路,它已经适用于一个实践环境,并且可以适用于其他坏境。
设计模式通常是对于某一类的软件设计问题的可重用的解决方案,将设计模式引入软件设计和开发过程,其目的就在于要充分利用已有的软件开发经验。
设计模式的种类很多,包括分布式编程模式、用户界面模式、数据模型模式三大类。目前流程的面向对象设计模式,仅1995年“gang of four”描述的就有二十多种,我们称之为GoF模式;与GoF模式相对应的另一种重要的设计模式是通用责任分配软件系列模式(GRASP)。
GRASP模式着重考虑设计类的原则及如何分配类的功能,而GoF模式则着重考虑设计的实现、类的交互和软件的质量。已经说,GoF模式就是符合GRASP模式要求的面向对象设计模式。
模式应该有以下特点:
(1) 在特定的场景下有可重用性,对相同类型不同问题的环境,其解决方案都有效。
(2) 可传授性,即问题出现的机会很多,解决问题的方案相同,人们相对可以接受。
(3) 有表示模式的名称。
设计模式主要有以下作用:
(1) 重用设计,重用设计比重用代码更有意义,它会自动带来代码的重用。
(2) 为设计提供共用的词汇,每个模式名就是一个设计词汇,其概念使得程序员间的交流更加方便。
(3) 在开发文档中采用模式词汇可以让其他人更容易理解你的想法,理解为什么你会这样做,你都做了些什么。编写开发文档也更加容易。
(4) 应用设计模式可以让重构系统变得容易,可确保开发正确的代码,并降低在设计或实现中出现错误的可能性,还可以为重写其他应用程序提供很好的系统框架。
(5) 正确使用设计模式,可以节省大量的时间。
1988年,勃兰特·梅耶(Bertrand Meyer)在他的著作《面向对象软件构造(ObjectOriented Software Construction)》中提出了开闭原则,它的原文是这样:“Softwareentities should be open for extension,but closed for modification”。
· 意思:软件模块应该对扩展开放,对修改关闭。
· 举例:在程序需要进行新增功能的时候,不能去修改原有的代码,而是新增代码,实现一个热插拔的效果(热插拔:灵活的去除或添加功能,不影响到原有的功能)。
· 目的:为了使程序的扩展性好,易于维护和升级。
· 意思:里氏代换原则是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
· 举例:球类,原本是一种体育用品,它的衍生类有篮球、足球、排球、羽毛球等等,如果衍生类替换了基类的原本方法,如把体育用品改成了食用品(那么软件单位的功能受到影响),就不符合里氏代换原则。
· 目的:对实现抽象化的具体步骤的规范。
· 意思:针对接口编程,而不是针对实现编程。
· 举例:以计算机系统为例,无论主板、CPU、内存、硬件都是在针对接口设计的,如果针对实现来设计,内存就要对应到针对某个品牌的主板,那么会出现换内存需要把主板也换掉的尴尬。
· 目的:降低模块间的耦合。
· 使用多个隔离的接口,比使用单个接口要好。
· 举例:比如:登录,注册时属于用户模块的两个接口,比写成一个接口好。
· 目的:提高程序设计灵活性。
1987年秋天由美国NortheasternUniversity的IanHolland提出,被UML的创始者之一[Booch]等普及。后来,因为在经典著作《 ThePragmatic Programmer》而广为人知。
· 意思:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
· 举例:一个类公开的public属性或方法越多,修改时涉及的面也就越大,变更引起的风险扩散也就越大。
· 目的:降低类之间的耦合,减少对其他类的依赖。
该原则由罗伯特·C·马丁(Robert C. Martin)于《敏捷软件开发:原则、模式和实践》一书中给出的。马丁表示此原则是基于汤姆·狄马克(Tom DeMarco)和Meilir Page-Jones的著作中的内聚性原则发展出的。
· 意思:一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
· 举例:该原则意思简单到不需要举例!
· 目的:类的复杂性降低,可读性提高,可维护性提高。
c++设计模式:
简单工厂模式
工厂模式有一种非常形象的描述,建立对象的类就如一个工厂,而需要被建立的对象就是一个个产品;在工厂中加工产品,使用产品的人,不用在乎产品是如何生产出来的。从软件开发的角度来说,这样就有效的降低了模块之间的耦合。
使用情景:
在不确定会有多少个处理操作时应该考虑使用简单工厂模式,如针对同样的接收到的数据,处理的逻辑可能会不同,可能以后还会增加新的操作。
案例:
如果实现计算器的功能时,对于同样的输入数据,可能执行加、减、乘、除,甚至其他的功能。因此可以抽象出一个操作的抽象类或是接口,提供一个统一的处理方法(此处为process),然后每种操作创建出一个子类出来。而判断具体使用哪个具体的实现类是在工厂类中进行判断的(将存放操作的变量传递给工厂的生产方法)。工厂类始终返回的是这个抽象类,这样如果对原有功能进行更改或是新添加新的功能,也不会对原来的其他类做修改,只编译修改的那个类或是新的类就可以了。
这样就做到了把耦合降到最低,同时也便于维护。
简单工厂:针对同样的数据,不同的操作用同一个接口
工厂方法:针对同样的数据,不同的操作用不同的接口
抽象工厂:针对不同的数据,不同的操作用不同的接口
策略模式:依赖c++的多态,抽象类的指针可以访问所有子类对象,(纯虚函数),可以用一个指针访问所有策略的实现类
单例模式:单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例单例模式(不能让一个程序打开两次 如:不能同时打开2个迅雷 迅雷用的单例模式)
访问者模式:适用于数据结构相对未定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由的演化。访问者模式使得增加新的操作变的很容易,就是增加一个新的访问者类。访问者模式将有关的行为集中到一个访问者对象中(做任何更改不需要修改基类,不依赖虚函数)
观察者模式:定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使他们能够自动更新自己。(QT的信号机制,Windows的消息机制都应用了观察者模式,还有订阅邮件,邮件到了就会给你发邮件)
建造者模式:使得产品内部表象可以独立地变化,客户不必知道产品内部组成的细节。可以强制实行一种分步骤进行的建造过程。用一个接口完成不同的操作,需要对客户的需求进行把握。(如:登陆QQ,自动选择所在地的服务器)
解释器模式:给定一个语言后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器。客户端可以使用这个解释器来解释这个语言中的句子。(如:360读取lua脚本,这个细节的实现就是解释器模式)
命令模式:把发出命令的责任和执行命令的责任分割开,委派给不同的对象允许请求的一方和发送的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否执行,何时被执行以及是怎么被执行的。(命令模式在客户端与服务器之间用的最多 (C/S架构))
模板模式:不同的子类可以以不同的方式实现这些抽象方法,
从而对剩余的逻辑有不同的实现。先制定一个顶级逻辑框架,
而将逻辑的细节留给具体的子类去实现。(适用于本地化,做一个软件,在日本是日文,美国是英语...)
桥接模式:将抽象化与实现化脱离,使得二者可以独立的变化,
也就是指在一个软件系统的抽象化和实现化之间使用组合聚合关系而不是继承关系,从而使两者可以独立的变化。(相当于配电脑去装机,把各个模块组合到一起)
适配器模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口原因不匹配而无法一起工作的两个类能够一起工作。
外观模式:外部与一个子系统的通信必须通过一个统一的外观对象进行。每一个子系统只有一个外观类,而且此外观类只有一个实例,也就是说它是一个单例模式。但整个系统可以有多个外观类。(多个子系统方法都需要一个外观类统一管理,用统一的接口方便消费者使用)
享元模式:享元模式大幅度的降低内存中对象的数量,使用享元模式主要是为了优化内存,相同功能可以并行使用。
原型模式:允许动态的增加或减少产品类,产品类不需要非得有任何事先确定的等级结构,原始模型模式适用于任何的等级结构。缺点是每一个类都必须配备一个克隆方法。
责任链模式:在责任链模式中,很多对象由每一个对象对其下家的引用而接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。处理者有两个选择:承担责任或者把责任推给下家。一个请求可以最终不被任何接收端对象所接受。(例如:晚上去上英语课,为了好开溜坐到了最后一排,哇,前面坐了好几个漂亮的MM哎,找张纸条,写上“Hi, 可以做我的女朋友吗?如果不愿意请向前传”,纸条就一个接一个的传上去了,糟糕,传到第一排的MM把纸条传给老师了)
中介者模式:中介者模式包装了一系列对象相互作用的方式,
使得这些对象不必相互明显作用。从而使他们可以松散偶合。
当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用。(如:TCP/IP打洞技术)
装饰模式:装饰模式以对客户端透明的方式扩展对象的功能是继承关系的一个替代方案,提供比继承更多的灵活性。动态给一个对象增加功能,这些功能可以再动态的撤消。增加由一些基本功能的排列组合而产生的非常大量的功能。
状态模式:意图是让一个对象在其内部状态改变的时候,其行为也随之改变。状态模式需要对每一个系统可能取得的状态创立一个状态类的子类。当系统的状态变化时,系统便改变所选的子类。(如:到了晚上12点要睡觉,到了早上8点要起床...这就是状态)
合成模式:将对象组织到树结构中,可以用来描述整体与部分的关系。合成模式就是一个处理对象的树结构的模式。合成模式把部分与整体的关系用树结构表示出来。(用于树状结构)
设计模式C++实现(1)——工厂模式
软件领域中的设计模式为开发人员提供了一种使用专家设计经验的有效途径。设计模式中运用了面向对象编程语言的重要特性:封装、继承、多态,真正领悟设计模式的精髓是可能一个漫长的过程,需要大量实践经验的积累。最近看设计模式的书,对于每个模式,用C++写了个小例子,加深一下理解。主要参考《大话设计模式》和《设计模式:可复用面向对象软件的基础》两本书。本文介绍工厂模式的实现。
工厂模式属于创建型模式,大致可以分为三类,简单工厂模式、工厂方法模式、抽象工厂模式。听上去差不多,都是工厂模式。下面一个个介绍,首先介绍简单工厂模式,它的主要特点是需要在工厂类中做判断,从而创造相应的产品。当增加新的产品时,就需要修改工厂类。有点抽象,举个例子就明白了。有一家生产处理器核的厂家,它只有一个工厂,能够生产两种型号的处理器核。客户需要什么样的处理器核,一定要显示地告诉生产工厂。下面给出一种实现方案。
[cpp] view plain copy
print?
1. enum CTYPE {COREA, COREB};
2. class SingleCore
3. {
4. public:
5. virtual void Show() = 0;
6. };
7. //单核A
8. class SingleCoreA: public SingleCore
9. {
10. public:
11. void Show() { cout<<"SingleCore A"< 12. }; 13. //单核B 14. class SingleCoreB: public SingleCore 15. { 16. public: 17. void Show() { cout<<"SingleCore B"< 18. }; 19. //唯一的工厂,可以生产两种型号的处理器核,在内部判断 20. class Factory 21. { 22. public: 23. SingleCore* CreateSingleCore(enum CTYPE ctype) 24. { 25. if(ctype == COREA) //工厂内部判断 26. return new SingleCoreA(); //生产核A 27. else if(ctype == COREB) 28. return new SingleCoreB(); //生产核B 29. else 30. return NULL; 31. } 32. };
这样设计的主要缺点之前也提到过,就是要增加新的核类型时,就需要修改工厂类。这就违反了开放封闭原则:软件实体(类、模块、函数)可以扩展,但是不可修改。于是,工厂方法模式出现了。所谓工厂方法模式,是指定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。
听起来很抽象,还是以刚才的例子解释。这家生产处理器核的产家赚了不少钱,于是决定再开设一个工厂专门用来生产B型号的单核,而原来的工厂专门用来生产A型号的单核。这时,客户要做的是找好工厂,比如要A型号的核,就找A工厂要;否则找B工厂要,不再需要告诉工厂具体要什么型号的处理器核了。下面给出一个实现方案。
[cpp] view plain copy
print?
1. class SingleCore
2. {
3. public:
4. virtual void Show() = 0;
5. };
6. //单核A
7. class SingleCoreA: public SingleCore
8. {
9. public:
10. void Show() { cout<<"SingleCore A"< 11. }; 12. //单核B 13. class SingleCoreB: public SingleCore 14. { 15. public: 16. void Show() { cout<<"SingleCore B"< 17. }; 18. class Factory 19. { 20. public: 21. virtual SingleCore* CreateSingleCore() = 0; 22. }; 23. //生产A核的工厂 24. class FactoryA: public Factory 25. { 26. public: 27. SingleCoreA* CreateSingleCore() { return new SingleCoreA; } 28. }; 29. //生产B核的工厂 30. class FactoryB: public Factory 31. { 32. public: 33. SingleCoreB* CreateSingleCore() { return new SingleCoreB; } 34. };
工厂方法模式也有缺点,每增加一种产品,就需要增加一个对象的工厂。如果这家公司发展迅速,推出了很多新的处理器核,那么就要开设相应的新工厂。在C++实现中,就是要定义一个个的工厂类。显然,相比简单工厂模式,工厂方法模式需要更多的类定义。
既然有了简单工厂模式和工厂方法模式,为什么还要有抽象工厂模式呢?它到底有什么作用呢?还是举这个例子,这家公司的技术不断进步,不仅可以生产单核处理器,也能生产多核处理器。现在简单工厂模式和工厂方法模式都鞭长莫及。抽象工厂模式登场了。它的定义为提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。具体这样应用,这家公司还是开设两个工厂,一个专门用来生产A型号的单核多核处理器,而另一个工厂专门用来生产B型号的单核多核处理器,下面给出实现的代码。
[cpp] view plain copy
print?
1. //单核
2. class SingleCore
3. {
4. public:
5. virtual void Show() = 0;
6. };
7. class SingleCoreA: public SingleCore
8. {
9. public:
10. void Show() { cout<<"Single Core A"< 11. }; 12. class SingleCoreB :public SingleCore 13. { 14. public: 15. void Show() { cout<<"Single Core B"< 16. }; 17. //多核 18. class MultiCore 19. { 20. public: 21. virtual void Show() = 0; 22. }; 23. class MultiCoreA : public MultiCore 24. { 25. public: 26. void Show() { cout<<"Multi Core A"< 27. 28. }; 29. class MultiCoreB : public MultiCore 30. { 31. public: 32. void Show() { cout<<"Multi Core B"< 33. }; 34. //工厂 35. class CoreFactory 36. { 37. public: 38. virtual SingleCore* CreateSingleCore() = 0; 39. virtual MultiCore* CreateMultiCore() = 0; 40. }; 41. //工厂A,专门用来生产A型号的处理器 42. class FactoryA :public CoreFactory 43. { 44. public: 45. SingleCore* CreateSingleCore() { return new SingleCoreA(); } 46. MultiCore* CreateMultiCore() { return new MultiCoreA(); } 47. }; 48. //工厂B,专门用来生产B型号的处理器 49. class FactoryB : public CoreFactory 50. { 51. public: 52. SingleCore* CreateSingleCore() { return new SingleCoreB(); } 53. MultiCore* CreateMultiCore() { return new MultiCoreB(); } 54. };
至此,工厂模式介绍完了。