23种设计模式
23种设计模式
现在大家常说的经典究竟是什么?有人说:经久不衰的万世之作,后人尊敬它称之为经典。也有人说:经典是指具有典范性、权威性的著作。还有人说:经典就是经过历史选择出来的“最有价值的书”。 在软件工程方面,勋章可能要颁给《设计模式:可复用面向对象软件的基础》。人们可以把设计模式的普及归功于《设计模式》一书,它不仅可以看作是设计模式运动的起始点,也可以看作是返回点;与设计模式相关多,但关于这个主题的大多数讨论都是围绕这本书中列出的设计模式。我们并不是排除其他模式,但这本书中提到的23个模式肯定可以称之为经典。
那么现在就简单介绍一下,《设计模式》一书中将23个模式分为三类,创建型模式、结构型模式、行为型模式。其中,创建型模式包括工厂方法模式、抽象工厂模式、建造者模式、原型模式、单例模式;结构型模式包括适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式、代理模式;行为型模式包括职责链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式、访问者模式。
1.工厂方法模式:将具体按钮的创建过程交给专门的工厂子类去完成,先定义一个抽象的按钮工厂类,再定义具体的工厂类来生成圆形按钮、矩形按钮、菱形按钮等,它们实现在抽象按钮工厂类中定义的方法。
2.抽象工厂模式:产品等级结构是产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类;产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。当系统所提供的工厂所需生产的具体产品并不是一个简单的对象,而是多个位于不同产品等级结构中属于不同类型的具体产品时需要使用抽象工厂模式。
3.建造者模式:无论是在现实世界中还是在软件系统中,都存在一些复杂的对象,它们拥有多个组成部分,如汽车,它包括车轮、方向盘、发送机等各种部件。而对于大多数用户而言,无须知道这些部件的装配细节,也几乎不会使用单独某个部件,而是使用一辆完整的汽车,可以通过建造者模式对其进行设计与描述,建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。
4.原型模式:在面向对象系统中,使用原型模式来复制一个对象自身,从而克隆出多个与原型对象一模一样的对象。在软件系统中,有些对象的创建过程较为复杂,而且有时候需要频繁创建,原型模式通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象,这就是原型模式的意图所在。
5. 单例模式:对于系统中的某些类来说,有时候只有一个实例很重要,如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。
6.适配器模式:在软件开发中采用类似于电源适配器的设计和编码技巧被称为适配器模式。 有时,现有的类可以满足客户类的功能需要,但是它所提供的接口不一定是客户类所期望的,这可能是因为现有类中方法名与目标类中定义的方法名不一致等原因所导致的。在这种情况下,现有的接口需要转化为客户类期望的接口,这样保证了对现有类的重用。如果不进行这样的转化,客户类就不能利用现有类所提供的功能,适配器模式可以完成这样的转化。
7.桥接模式: 对于有两个变化维度(即两个变化的原因)的系统,采用根据实际对两个变化维度进行组合来进行设计系统中类的个数更少,且系统扩展更为方便。采用根据实际对两个变化维度进行组合即是桥接模式的应用。桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合,减少了代码编写量。
8.组合模式:对于树形结构,当容器对象的某一个方法被调用时,将遍历整个树形结构,寻找也包含这个方法的成员对象(可以是容器对象,也可以是叶子对象)并调用执行,由于容器对象和叶子对象在功能上的区别,在使用这些对象的客户端代码中必须有区别地对待容器对象和叶子对象,而实际上大多数情况下客户端希望一致地处理它们,因为对于这些对象的区别对待将会使得程序非常复杂组合模式描述了如何将容器对象和叶子对象进行递归组合,使得用户在使用时无须对它们进行区分,可以一致地对待容器对象和叶子对象。
9.装饰模式:装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。
10.外观模式:外观模式要求一个子系统的外部与其内部的通信通过一个统一的外观对象进行,外观类将客户端与子系统的内部复杂性分隔开,使得客户端只需要与外观对象打交道,而不需要与子系统内部的很多对象打交道,其目的在于降低系统的复杂程度,它从很大程度上提高了客户端使用的便捷性,使得客户端无须关心子系统的工作细节,通过外观角色即可调用相关功能。
11.享元模式:在享元模式中,可以共享的相同内容是内部状态,而那些需要外部环境来设置的不能共享的内容是外部状态,由于区分了内部状态和外部状态,因此可以通过设置不同的外部状态使得相同的对象可以具有一些不同的特征,而相同的内部状态是可以共享的。
12.代理模式:在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务。
13.解释器模式:给定一个语言,定义它的文法一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。也就是说,如果你想自己开发一种语言来解释执行某些语言的特定语法,可以考虑使用解释器模式。该模式对于开发人员来说,基本上都用不到,除非想自己开发一种语言,解释器模式真正开发起来很难,就相当于自己开发了一种语言给别人用。
14.模板方法模式:在模板方法模式中,在父类中提供了一个定义算法框架的模板方法,还提供了一系列抽象方法、具体方法和钩子方法,其中钩子方法的引入使得子类可以控制父类的行为。最简单的钩子方法就是空方法,代码如下:
[csharp] view plain copy
public virtual void Display() { }
当然也可以在钩子方法中定义一个默认的实现,如果子类不覆盖钩子方法,则执行父类的默认实现代码。另一种钩子方法可以实现对其他方法进行约束,这种钩子方法通常返回一个bool类型,即返回true或false,用来判断是否执行某一个基本方法。
15.职责链模式:职责链可以是一条直线、一个环或者一个树形结构,最常见的职责链是直线型,即沿着一条单向的链来传递请求。链上的每一个对象都是请求处理者,职责链模式可以将请求的处理者组织成一条链,并使请求沿着链传递,由链上的处理者对请求进行相应的处理,客户端无须关心请求的处理细节以及请求的传递,只需将请求发送到链上即可,将请求的发送者和请求的处理者解耦。
16.命令模式:命令模式的结构很简单,但对于消除代码间的耦合却有着重要的影响。我们都学过C 语言,在 C 语言中我们经常使用回调函数,而命令模式是回调( callback )的面向对象的替代物。从最直观的角度来看,命令模式就是一个函数对象:一个作为对象的函数。通过将函数封装为对象,就能够以参数的形式将其传递给其他函 数或者对象,告诉他们在履行请求的过程中执行特定的操作。可以说,命令模式是携带行为信息的信使。命令模式对于构建 GUI 应用有特别重要的意义,比如菜单的响应,使用 MFC 时我们通过一系列的 on_command 宏来关联菜单操作,而如果采用命令模式和工厂模式,将可以得到更精巧的实现。
17.迭代器模式:提供一种方法顺序的访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。一般情况,我们自己开发时很少自定义迭代器,因为java本身已经把迭代器做到内部中了,比如,常用的list和set中都内置了迭代器。当然,如果真有这种需求需要我们自定义迭代器的话,可以参考jdk的迭代器实现方式来实现自己的迭代器。迭代器是可以从前往后,或者从后往前遍历的。为遍历不同聚集结构提供如:开始,下一个,是否有下一个,是否结束,当前哪一个等等的一个统一接口。
18.中介者模式:在软件的开发过程中,势必会碰到这样一种情况,多个类或多个子系统相互交互,而且交互很繁琐,导致每个类都必须知道它需要交互的类,这样它们的耦合会显得异常厉害,这严重违反低耦合的规则。而中介者模式用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
19.备忘录模式:备忘录模式其实就是给我们的应用程序一次撤销的机会。使用过word的我们都知道用“Ctrl+Z”进行撤销,用过PS的人更是不会忘记,应该来说基本上所有的带编辑功能的软件毫无例外都提供了撤销的功能,撤销功能给了我们1次或N次返回的机会,准确地说应该是恢复之前状态的机会。我们自己开发的软件有时候有需要撤销的功能,比如在网络通信中,常常会因为不可预知的错误就导致程序出错,这时候,要是能恢复到上一个正确的状态就太好了,这样可以省去不上功夫。而备忘录模式就是用来解决这个问题的。
20.观察者模式:在观察者模式中,一个被观察者管理所有相依于它的观察者物件,并且在本身的状态改变时主动发出通知。这通常通过呼叫各个观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。其适用场景有:当一个抽象模型有两个方面, 其中一个方面依赖于另一方面,将这二者封装在独立的对象中以使它们可以各自独立地改变和复用;当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变;当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。
21.状态模式:状态模式在实际使用中比较多,适合"状态的切换"。因为我们经常会使用if else if else 进行状态切换,如果针对状态的判断切换反复出现,我们就必须要联想到是否可以采取状态模式了。不只是根据状态,也有根据属性。如果某个对象的属性不同,对象的行为就不一样,这点在数据库系统中出现频率比较高,我们经常会在一个数据表的尾部, 加上property属性含义的字段,用以标识记录中一些特殊性质的记录,这种属性的改变(切换)又是随时可能发生的,就有可能要使用状态模式。
22. 策略模式:简单介绍一下其组成,包括抽象策略角色、具体策略角色、环境角色,抽象策略角色定义了所有算法的一个公共接口,各种不同的算法(即具体策略)以不同的方式实现这个接口,一般使用接口或抽象类实现。具体策略角色提供了具体的算法实现。环境角色的内部维护一个策略类的实例,可在运行时动态设定算法(具体策略),负责跟Strategy之间的交互和数据传递。
23.访问者模式:在实际的软件开发过程中,有时候对同一个对象可能会有不同的处理,对相同元素对象也可能存在不同的操作方式,比如处方单,划价人员要根据它来划价,药房工作者要根据它来给药。而且可能会随时增加新的操作,如医院增加新的药物。但是这里有两个元素是保持不变的或者说很少变,划价人员和药房工作中,变的只不过是他们的操作。访问者模式的目的是封装一些施加于某种数据结构元素之上的操作,一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变。为不同类型的元素提供多种访问操作方式,且可以在不修改原有系统的情况下增加新的操作方式。
既然这23个模式被称之为经典,那么我们就应该将其铭记于心,最后依然将一句令人深思的话送个大家:有些事情,不是看到希望才去坚持,而是坚持了,才看得到希望!