前引:文章来自四人帮的设计模式一书,写文不为别的,只是作为笔记和学习理解所用。
1. 什么是设计模式
在四人帮的书中,引用了Christopher Alexander说过的一句话:“每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题解决方案的核心”。一般而言,一个模式具有四个基本要素,如下:
(1)模式名称:一个助记名,它用一两个词来描述模式的问题,解决方案和效果。
(2)问题:描述了应该何时使用设计模式。它解释了设计问题和问题存在的前因后果,它可能描述了特定的设计问题,如怎么用对象表示算法。
(3)解决方案:描述了设计的组成部分,他们之间的相互关系和各自的职责和协作方式。
(4)效果:描述了模式应用的效果及使用模式应权衡的问题。尽管我们描述设计决策时,并不总提到模式效果,但它们对于评价设计学则和理解使用模式的代价及好处具有重要意义。软件效果大多关注的是时间和空间的衡量,它们也表述了语言和实现问题。因为复用是面向对象的要素之一,所以模式效果包括对系统的灵活性、扩充性或可以可移植性的影响。显示地列出这些效果对理解和评价这些模式很有帮助。
2. 常用的23种设计模式列表
一般模式是根据两条准则对模式进行分类,第一是目的准则,即模式是用来完成什么工作的。模式依据其目的可分为创建型、结构型、行为型三种。创建型模式与对象的创建有关,结构型模式处理类或者对象的组合;行为模型对类或对象怎样交互和怎样分配职责进行描述。第二是范围准则,指定模式主要是用于类还是用于对象。类模式处理类和子类之间的关系,这些关系通过继承建立,是静态的。在编译时刻便确定下来了。对象模式处理对象间的关系,这些关系在运行时刻是可以变化的,更具动态性。在某种意义上来说,所有的模式都适用继承机制,所以“类模式”只指哪些集中于处理类间关系的模式,而大部分模式都属于对象模式的范畴。
创建型类模式将对象的部分创建工作延迟到子类,而创建型对象模式则将它延迟到另一个对象种。结构型类模式使用基层机制来组合类,而结构型对象模式则描述了对象的组装方式。行为型模式使用继承描述算法和控制流,而行为型模式对象则描述一组对象怎样协作完成单个对象无法完成的任务。
3.设计模式怎样解决设计问题
设计模式采用多种方法解决面向对象设计者经常碰到的问题,简要列出几个问题,并且用设计模式解决它们的方法。
(1)需找合适的对象:面向对象程序由对象组成,对象包括数据和对数据进行操作的过程,过程通常称为方法或操作。对象在收到客户的请求后,执行相应的操作。客户请求是使对象执行操作的唯一方法,操作又是对象改变内部数据的唯一方法。
(2)决定对象的粒度:对象在大小和数目上变化极大,设计模式中讲述了如何用对象来表示完整的子系统。
(3)指定对象的接口:对象声明的每一个操作指明操作名、作为参数的对象和返回值,称为操作的型构。对象操作所定义的所有操作型构的集合称为该对象的接口。对象接口描述了该对象所能接受的所有请求的集合。
(4)描述对象的实现:c++中接口继承的标准方法是共有继承一个虚函数的类。c++中纯接口继承接近于公有继承抽象类。纯实现继承或纯类继承接近于似有继承。对接口编程而不是对类编程,类继承是一个通过复用父类功能而扩展应用功能的基本机制,允许你从旧对象中快速定义新对象,它允许你从已存在的类中继承所需要的绝大部分功能,从而无需任何代价获取新的实现。当继承被恰当使用时,所有从抽象类中导出的类将共享该抽象类的接口。这意味着子类仅添加或重定义操作,而没有隐藏父类的操作,所有的子类都能响应抽象类接口中的请求,从而子类的类型都是抽象类的子类型。
只根据抽象类中的接口操作对象有两个好处:
- 客户无需知道它们使用对象的特定类型,只须对象有客户所期望的接口。
- 客户无须知道它们使用对象是用什么类来实现,它们只须知道定义接口的抽象类。
针对接口编程,而不是针对实现编程。不将变量声明为某个特定的具体类的实例对象,而是让它遵从抽象类所定义的接口,是设计模式常见的主题。当你不得不在系统某个地方实例化具体的类时,创建型模式可以帮你。通过抽象对象的创建过程,这些模式提供不同方式以是实例化建立接口和实现的透明链接。创建型模式确保你的系统是采用针对接口的方式书写,而不是针对实现而书写的。
(5)运用复用机制:面向对象系统中复用的两种最常用的技术是类继承和对象组合。类继承允许你根据其他类的实现定义一个类的实现,这种通过生成子类的复用通常称为白箱复用。(就如测试中的白盒测试,我们知道里面的实现细节,这里我们知道继承的父类的具体细节)。而对象组合要求对象具有良好定义的接口,这种复用风格被称为黑箱复用。类继承是在编译时刻静态定义的,且可直接使用,因为程序设计语言直接支持类继承,类继承可以较方便地改变被复用地实现,当一个子类重定义一些而不是全部操作时,它也能影响它所继承的操作,只要在这些操作中调用了被重定义的操作。但是类继承也有一些不足,首先,因为继承在编译时刻就定义了,所以无法在运行时刻改变从父类继承的实现,更糟糕的是,父类至少定了部分子类的具体表示。因为继承对子类揭示了其父类的实现细节,所以继承常被认为“破坏了封装”。子类中的实现与父类有如此紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。当你需要复用子类时,实现上的以耐性就会产生一些问题,如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。一个可用的解决方法就是只继承抽象类。对象组合是通过获得对其他对象的引用而在运行时刻动态定义的。组合要求对象遵守彼此的接口约定,进而要求更仔细得定义接口,而这些接口并不妨碍你将一个对象和其他对象一起使用。这还会产生良好地结果,因为对象只能通过接口访问,所以我们并不破坏分装性,只要类型一致,运行时刻还可以用一个对象来替换另一个对象,更进一步,因为对象的实现是基于接口写的,所以是实现上存在较少的依赖关系。对象组合对系统设计还有另一个作用,即优先使用对象组合有助于你保持每个类被封装。所以优先使用组合,而不是类继承。委托是一种组合方法它使组合具有与继承相同的复用能力。在委托方式下,有两个对象参与处理一个请求,接受请求的对象将操作委托给它的代理者。
委托可以如下代码所示:
class window {
public:
Area(){return m_Rectangle->Area();}
private:
Rectangle * m_Rectangle;
}
class Rectangle{
public:
Area(){ return width * height;}
private:
int width;
int height;
}
(6)继承与参数类型的比较:另一种功能复用技术是参数化类型,也就是类属或模版。它允许你在定义一个类型时并不指定该类型所用到的其他所有类型。未经指定的类型在使用时以参数形式提供。
(7)关联运行时刻和编译时刻的结构:聚合和相识时这两种结构的代表。聚合意味着一个对象拥有另一个对象,它们具有相同的生命期。相识意味着一个对象仅仅知道另一个对象,有时相识也被称为“关联”或“引用”关系。相识的对象可能请求彼此的操作,但是它们不为对方负责。相识是一种比聚合要弱的关系。c++中聚合通过表示真正的实例的成员变量来实现。
(8)设计应支持变化:获得最大限度的复用的关键在于对新需求和已有需求发生变化时的预见性,要求你的系统设计要能够相应得改进。一般导致重新设计原因,以及解决这些问题得设计模式:
- 通过显示得指定一个类来创建对象,在创建对象时指定类名将使你受特定实现而不是特定接口得约束。这会使得未来得变化更加复杂。要避免这种情况,应该间接地创建对象。设计模式:abstract factory,factory method, prototype.
- 对特殊操作地依赖,当你指定一个特殊操作时,完成该请求地方式就固定下来,为了避免把请求代码写死,你可以在编译时刻或运行时刻很方便地改变相应请求地方法。设计模式:chain of responsibility, command。
- 对硬件和软件平台的依赖,外部的操作系统接口和应用程序接口在不同的软硬件平台上是不同的。依赖于特定的平台的软件将很难移植到其他平台上,甚至很难跟上本定平台的更新,所以设计室限制其平台相关性就很重要。设计模式:abstract factory,bridge。
- 对对象表示或实现的依赖,知道对象怎样表示,保存、定位或实现的客户在对象发生时可能需要变化。设计模式:abstract factory,bridge,memento,proxy。
- 算法依赖,算法在开发和复用时常常被扩展、优化和替换。依赖于某个特定算法的对象在苏犯法发生变化时不得不变化。设计模式:builder、iterator、strategy、template method。
- 紧耦合,紧耦合的类很难独立地被复用。因为它们互相依赖。设计模式使用抽象耦合和分层技术来提高系统地松散耦合性。设计模式:abstract factory、command、facade、mediator、observer、chain of responsibility。
- 通过生成子类来扩充功能。设计模式:bridge、chain of responsibility、composite、decorator、observer、strategy。
- 不能方便地对类修改。设计模式:adapter、decorator、visitor。