设计模式

软件设计模式来源于Christopher Alexander的建筑学模式和对象运动。根据Alexander的观点,模式就是一个对于特定的系统的通用解决方案本身的重复。对象运动关注于将现实世界模化为软件内部的关系。基于这两个原因,软件设计模式对于真实世界的物体而言同样应当是可以重复的。这篇文章呈现了现实的世界中的非软件的模式实例,这些模式来源于《设计模式-可复用面向对象软件的基础》(Design Patterns - Elements of Reusable Object-Oriented Software)[13]一书。这篇文章也举例讨论了模式语言对非软件的表现力和设计模式的练习。

在软件行业中,模式支持者的团体正在扩大。模式发展的起源可以在建筑师Christopher Alexander的著作中找到,他认为模式是世界上特定系统的通用解决方案。他描述的模式可以在日常的建筑物中观察到。《模式语言》(A Pattern Language)中的每个模式都包含了一张该模式原始范例的图片。

虽然物质是主流世界的观点,而模式为软件世界所信奉,模式也有其体现事物发展的根源。不幸的是软件设计模式的例子不象Alexander模式那么丰富,因为软件设计表现的是精致的构思而不是那些最初产生的想法。当今大多数软件的专有性限制了我们接触一流设计的机会。

根据Alexander的说法,现实世界中模式总是重复自己,因为在一个特定的环境下,它们总是很好地适应现有的环境因素。在软件中,要么现实世界的问题被完全地模式化,要么现实世界的物体被转换成为硬件和软件,用来产生现实世界的结果。既然软件设计模式根源于Alexander的样式和对象,那么在现实世界中找到软件设计模式也是很正常的。这并不是说软件设计模式是现实世界事物的必然模型,而是说在契合的对象之间相互影响的关系可以在"现实世界"和软件对象中同样地观察到。为了验证这个假设,我们将为每一种设计模式找出一个现实世界的例子来。

创建型模式


作者(指《设计模式》的作者-译注,下同)总结了五种创建型模式。创建型模式的例子可以在制造业,快餐,生物和行政机构中找到。

抽象工厂(Abstract Factory)举例

抽象工厂的目的是要提供一个创建一系列相关或相互依赖对象的接口,而不需要指定它们具体的类。这种模式可以在日本汽车制造厂所使用的金属冲压设备中找到。这种冲压设备可以制造汽车车身部件。同样的机械用于冲压不同的车型的右边车门、左边车门、右前挡泥板、左前挡泥板和引擎罩等等。通过使用转轮来改变冲压盘,这个机械产生的具体类可以在三分钟内改变。

screen.width-500)this.style.width=screen.width-500;">


图1:抽象工厂的冲压例子

生成器(Builder)举例

生成器模式将复杂对象的构建与对象的表现分离开来,这样使得同样的构建过程可以创建出不同的表现。这种模式用于快餐店制作儿童餐。典型的儿童餐包括一个主食,一个辅食,一杯饮料和一个玩具(例如汉堡、炸鸡、可乐和玩具车)。这些在不同的儿童餐中可以是不同的,但是组合成儿童餐的过程是相同的。无论顾客点的是汉堡,三名治还是鸡肉,过程都是一样的。柜台的员工直接把主食,辅食和玩具放在一起。这些是放在一个袋子中的。饮料被倒入杯中,放在袋子外边。这些过程在相互竞争的餐馆中是同样的。

screen.width-500)this.style.width=screen.width-500;">
图2:使用儿童餐作为例子的生成器模式的对象作用表

工厂方法(Factory Method)

工厂方法定义一个用于创建对象的接口,但是让子类决定实例化哪个类。压注成型演示了这种模式。塑料玩具制造商加工塑料粉,将塑料注入到希望形状的模具中。玩具的类别(车,人物等等)是由模具决定的。

screen.width-500)this.style.width=screen.width-500;">
图3:使用注入成型为例子的工厂方法的对象图
原型(Prototype)举例

原型模式使用原型实例指定创建对象的种类。新产品的原型通常是先于全部产品建立的,这样的原型是被动的,并不参与复制它自己。一个细胞的有丝分裂,产生两个同样的细胞,是一个扮演主动角色复制自己原型的例子,这演示了原型模式。一个细胞分裂,产生两个同样基因型的细胞。换句话说,细胞克隆了自己。

screen.width-500)this.style.width=screen.width-500;">
图4:使用细胞分裂例子的原型模式对象图
单件(Singleton)举例

单件模式确保一个类仅有一个实例,并提供一个访问它的全局访问点。单件模式是模仿单集命名的,单集的定义是每个集合仅含有一个元素。美国总统的职位是单件,美国宪法规定了总统的选举,任期以及继任的顺序。这样,在任何时刻只能由一个现任的总统。无论现任总统的身份为何,其头衔"美利坚美利坚合众国总统"是访问这个职位的人的一个全局的访问点。

screen.width-500)this.style.width=screen.width-500;">
图5:使用总统例子的单件模式对象图
结构性模式


作者总结了七个结构型模式,这些模式的例子可以在工具、住宅配线、数学、节日传统、零售目录和银行业中找到。

适配器(Adapter)举例

适配器模式允许将一个类的接口转换成客户期望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。扳手提供了一个适配器的例子。一个孔套在棘齿上,棘齿的每个边的尺寸是相同的。在美国典型的边长为1/2\'\'和1/4\'\'。显然,如果不使用一个适配器的话,1/2\'\'的棘齿不能适合1/4\'\'的孔。一个1/2\'\'至1/4\'\'的适配器具有一个1/2\'\'的阴槽来套上一个1/2\'\'的齿,同时有一个1/4的阳槽来卡入1/4\'\'的扳手。

screen.width-500)this.style.width=screen.width-500;">
图6:使用扳手适配器例子的适配器对象图
桥接(Bridge)举例

桥接模式将抽象部分与它的实现分离,使它们能够独立地变化。一个普通的开关控制的电灯、电风扇等等,都是桥接的例子。开关的目的是将设备打开或关闭。实际的开关可以是简单的双刀拉链开关,也可以是调光开关。

screen.width-500)this.style.width=screen.width-500;">
图7:使用电子开关例子的桥接对象图

组合(Composite)例子

组合模式将对象组合成树形结构以表示"部分-整体"的层次结构。让用户一致地使用单个对象和组合对象。虽然例子抽象一些,但是算术表达式确实是组合的例子。算术表达式包括操作数、操作符和另一个操作数。操作数可以是数字,也可以是另一个表达式。这样,2+3和(2+3)+(4*6)都是合法的表达式。

screen.width-500)this.style.width=screen.width-500;">
图8:使用算术表达式例子的组合模式对象图
装饰(Decorator)举例

装饰模式动态地给一个对象添加额外的职责。不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。

screen.width-500)this.style.width=screen.width-500;">
图9:使用有画框的画作为例子的装饰模式对象图
外观(Facade)举例

外观模式为子系统中的接口定义了一个统一的更高层次的界面,以便于使用。当消费者按照目录采购时,则体现了一个外观模式。消费者拨打一个号码与客服代表联系,客服代表则扮演了这个"外观",他包含了与订货部、收银部和送货部的接口。

screen.width-500)this.style.width=screen.width-500;">
图10:使用电话订货例子的外观模式对象图
享元(Flyweight)举例

享元模式使用共享技术有效地支持大量细粒度的对象。公共交换电话网(PSTN)是享元的一个例子。有一些资源例如拨号音发生器、振铃发生器和拨号接收器是必须由所有用户共享的。当一个用户拿起听筒打电话时,他不需要知道使用了多少资源。对于用户而言所有的事情就是有拨号音,拨打号码,拨通电话。

screen.width-500)this.style.width=screen.width-500;">


图11:使用拨号音发生器例子的享元模式对象图

代理(Proxy)模式


代理模式提供一个中介以控制对这个对象的访问。一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。

screen.width-500)this.style.width=screen.width-500;">
图12:使用银行存单例子的代理模式对象图

 

行为模式


作者总结了十一种行为模式。这些模式可以在硬币分类银行、餐馆订餐、音乐、运输、汽车修理、自动售货机和家庭建筑中找到例子。

职责链(Chain of Responsibility)举例

职责链模式使得多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。机械硬币分拣银行使用职责链。这里并不是为每一种硬币分配一个滑槽,而是仅使用一个滑槽。当硬币落下时,硬币被银行内部的机械导向至适当的接收盒。

screen.width-500)this.style.width=screen.width-500;">
图13:使用硬币分拣例子的职责链模式对象图
命令(Command)模式

命令模式将一个请求封装为一个对象,从而使你可以使用不同的请求对客户进行参数化。用餐时的账单是命令模式的一个例子。服务员接受顾客的点单,把它记在账单上封装。这个点单被排队等待烹饪。注意这里的"账单"是不依赖于菜单的,它可以被不同的顾客使用,因此它可以添入不同的点单项目。

screen.width-500)this.style.width=screen.width-500;">
解释器(Interpreter)举例

解释器模式定义了特定语言的文法表示和解释该文法的解释器。音乐家是解释器的例子。音阶和它的持续时间可以用五线谱上的符号表示。这些符号就是音乐语言[14]。音乐家按照乐谱演奏,就可以反复重现同样的音乐。

screen.width-500)this.style.width=screen.width-500;"> 迭代器(Iterator)举例

迭代器提供一种方法顺序访问一个集合对象中各个元素,而又不需要暴露该对象的内部表示。在早期的电视机中,一个拨盘用来改变频道。当改变频道时,需要手工转动拨盘移过每一个频道,而不论这个频道是否有信号。现在的电视机,使用[后一个]和[前一个]按钮。当按下[后一个]按钮时,将切换到下一个预置的频道。想象一下在陌生的城市中的旅店中看电视。当改变频道时,重要的不是几频道,而是节目内容。如果对一个频道的节目不感兴趣,那么可以换下一个频道,而不需要知道它是几频道。

screen.width-500)this.style.width=screen.width-500;">
中介者(Mediator)举例

中介者模式用一个中介对象来控制一系列的对象交互。通过中介者实现各个对象之间的松散耦合,而不是彼此直接作用。机场的控制塔很好地演示了这种模式。降落或者起飞的飞机直接与塔台通讯,而不是彼此间直接通讯。谁可以起飞或降落是由塔台决定的。这里需要注意的是塔台并不控制整个飞行过程。它只负责飞机在机场附近的区域。

screen.width-500)this.style.width=screen.width-500;">
图17:使用训练中心为例子的中介者模式
备忘录(Memento)举例

备忘录模式捕获并且在外部保存一个对象的内部状态,使得以后可以将对象恢复到该状态。这种模式通常体现在你自己修理汽车的刹车时。首先移开两边的挡板,露出左右刹车片。只能卸下一片,这时另一片作为一个备忘录来表明刹车是怎样安装的。在这片修理完成后,才可以卸下另一片。当第二片卸下时,第一片就成了备忘录。

screen.width-500)this.style.width=screen.width-500;">
观察者(Observer)模式


观察者定义了对象间一对多的关系,当一个对象的状态变化时,所有依赖它的对象都得到通知并且自动地更新。拍卖演示了这种模式。每个投标人都有一个标有数字的牌子用于出价。拍卖师开始拍卖时,他观察是否有牌子举起出价。每次接受一个新的出价都改变了拍卖的当前价格,并且广播给所有的投标人进行新的出价。

screen.width-500)this.style.width=screen.width-500;">
状态(State)模式


状态模式允许一个对象在其内部状态改变时改变它的行为。这种模式可以在自动售货机上观察到。自动售货机的状态包括列商品清单,收款,找钱和选择商品等几种状态。当投入硬币并选择了一个商品时,自动售货机可以完成以下几种操作,包括:送出商品不找钱、送出商品并找钱、由于投币不足不送出商品、由于商品售完不送出商品。

screen.width-500)this.style.width=screen.width-500;">
策略(Strategy)模式


策略模式定义了一系列可以相互替换的算法。不同的到飞机场去的方式就是一个策略模式的例子。有几种选择:自己开车、坐出租车、乘机场班车、乘公共汽车或使用专车服务等等。对于某些机场,地铁和直升机也是可能的选择。任何一种方式都可以把你送到机场,它们可以相互代替。你必须根据价格、便利性和时间做出选择。

screen.width-500)this.style.width=screen.width-500;"> 模板方法(Template Method)举例

模板方法定义了一个操作中算法的骨架,而将一些步骤延迟到子类中。房屋建筑师在开发孪钅渴被崾褂媚0宸椒āR桓龅湫偷墓婊ㄒ恍┙ㄖ矫嫱迹扛銎矫嫱继逑至瞬煌糠帧T谝桓銎矫嫱贾校鼗⒔峁埂⑸舷滤妥呦叨杂诿扛龇考涠际且谎摹V挥性诮ㄖ暮笃诓趴加胁畋鸲瞬煌姆课菅健?

screen.width-500)this.style.width=screen.width-500;">
访问者(Visitor)举例

访问者模式表示一个作用于对象结构中各元素的操作,定义这个操作并不会改变元素的类。这种模式可以在出租车公司的运转中观察到。当一个人给出租车公司打电话时,他(她)就成了公司所有顾客的一员。然后公司指定一辆车去服务(接受访问者)。在进入出租车之后,这个人(访问者)就不再控制他(她)的旅程了,而是由出租车(驾驶员)负责。

screen.width-500)this.style.width=screen.width-500;">
意义


软件设计模式有许多非软件的例子存在。也许有人想知道这些例子的实际意义。非软件例子有助于增进模式语言的表达力和辅助模式的学习。

增加模式语言的表达能力

Alexander觉得真正的模式要融入一种通用的语言以便所有人都能够分享。在软件设计的人群中,模式被认为是在同事之间一种约定俗成的开发方式。模式提供了一种比模块、过程和对象更高层次的概念。

一种语言中至关重要的因素是同语言形象所对应的心灵影像。在一种语言中,仅当一个人能够领会一个符号的含义,能够在心里描绘出这种含义时,这个符号的外形才是有意义的。Alexander没有忽视模式语言的这种重要特征,他规定:一种语言只有在它所产生的建筑类型能够被具体地看到之后,这种语言才是完全形态化的。在软件设计中,Richle和Züllighoven认识到具体的例子在指导我们对应用领域的理解的重要性。

如果软件设计模式成为程序员中通用的语言,其基础则是统一的含义。如果设计决定下达了,但是没有被理解,则设计师被迫通过假设来完成工作。平凡的例子更便于理解,其原因在于人们必须在记忆中找到相关联的内容才能够理解。在广泛使用模式的AG Communication Systems公司的项目中,常常使用非软件例子来解释模式之间的关系。这个例子有助于在设计师间提供统一的理解。通过在设计过程的先期建立统一的理解,使得在整个项目生命周期中,设计师间的沟通更加容易。

结论


在非软件例子中软件设计模式的体现表明了模式不是局限于特定领域的。软件设计师可以从这些日常事物的模式举例中受益,哪怕这些例子并不是以程序设计语言表达的。这篇文章尽可能举一些大部分人所熟悉的例子(尽管某些倾向于北美文化)。通过对共同的经历的描述,这些例子有助于对特定的设计模式的理解,并且能够帮助对设计模式的学习。

你可能感兴趣的:(设计模式)