前端设计模式

一、单例模式

  1. 单例模式的定义是:

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏 览器中的 window 对象等。在 JavaScript 开发中,单例模式的用途同样非常广泛。试想一下,当我 们单击登录按钮的时候,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,无论单击多少 次登录按钮,这个浮窗都只会被创建一次,那么这个登录浮窗就适合用单例模式来创建。

二、策略模式

俗话说,条条大路通罗马。在美剧《越狱》中,主角 Michael Scofield 就设计了两条越狱的 道路。这两条道路都可以到达靠近监狱外墙的医务室。

同样,在现实中,很多时候也有多种途径到达同一个目的地。比如我们要去某个地方旅游, 可以根据具体的实际情况来选择出行的线路。

  • 如果没有时间但是不在乎钱,可以选择坐飞机。
  • 如果没有钱,可以选择坐大巴或者火车。
  • 如果再穷一点,可以选择骑自行车。

在程序设计中,我们也常常遇到类似的情况,要实现某一个功能有多种方案可以选择。比如 一个压缩文件的程序,既可以选择 zip 算法,也可以选择 gzip 算法。

这些算法灵活多样,而且可以随意互相替换。这种解决方案就是本章将要介绍的策略模式。

策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

三、代理模式

代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。

代理模式是一种非常有意义的模式,在生活中可以找到很多代理模式的场景。比如,明星都 有经纪人作为代理。如果想请明星来办一场商业演出,只能联系他的经纪人。经纪人会把商业演 出的细节和报酬都谈好之后,再把合同交给明星签。

代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身 对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之 后,再把请求转交给本体对象。如图 6-1 和图 6-2 所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Do7zSbLz-1644760209459)(设计模式.assets/image-20220211172554432.png)]

四、迭代器模式

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象 的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即 使不关心对象的内部构造,也可以按顺序访问其中的每个元素。

目前,恐怕只有在一些“古董级”的语言中才会为实现一个迭代器模式而烦恼,现在流行的 大部分语言如 Java、Ruby 等都已经有了内置的迭代器实现,许多浏览器也支持 JavaScript 的 Array.prototype.forEach

五、发布—订阅模式

发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状 态发生改变时,所有依赖于它的对象都将得到通知。在 JavaScript 开发中,我们一般用事件模型 来替代传统的发布—订阅模式。

现实中的发布-订阅模式

不论是在程序世界里还是现实生活中,发布—订阅模式的应用都非常之广泛。我们先看一个 现实中的例子。

小明最近看上了一套房子,到了售楼处之后才被告知,该楼盘的房子早已售罄。好在售楼 MM 告诉小明,不久后还有一些尾盘推出,开发商正在办理相关手续,手续办好后便可以购买。 但到底是什么时候,目前还没有人能够知道。

于是小明记下了售楼处的电话,以后每天都会打电话过去询问是不是已经到了购买时间。除 了小明,还有小红、小强、小龙也会每天向售楼处咨询这个问题。一个星期过后,售楼 MM 决 定辞职,因为厌倦了每天回答 1000 个相同内容的电话。

当然现实中没有这么笨的销售公司,实际上故事是这样的:小明离开之前,把电话号码留在 了售楼处。售楼 MM 答应他,新楼盘一推出就马上发信息通知小明。小红、小强和小龙也是一 样,他们的电话号码都被记在售楼处的花名册上,新楼盘推出的时候,售楼 MM 会翻开花名册, 遍历上面的电话号码,依次发送一条短信来通知他们。

发布-订阅模式的作用

在刚刚的例子中,发送短信通知就是一个典型的发布—订阅模式,小明、小红等购买者都是 订阅者,他们订阅了房子开售的消息。售楼处作为发布者,会在合适的时候遍历花名册上的电话 号码,依次给购房者发布消息。

可以发现,在这个例子中使用发布—订阅模式有着显而易见的优点。

  • 购房者不用再天天给售楼处打电话咨询开售时间,在合适的时间点,售楼处作为发布者 会通知这些消息订阅者。
  • 购房者和售楼处之间不再强耦合在一起,当有新的购房者出现时,他只需把手机号码留 在售楼处,售楼处不关心购房者的任何情况,不管购房者是男是女还是一只猴子。 而售 楼处的任何变动也不会影响购买者,比如售楼 MM 离职,售楼处从一楼搬到二楼,这些 改变都跟购房者无关,只要售楼处记得发短信这件事情。

第一点说明发布—订阅模式可以广泛应用于异步编程中,这是一种替代传递回调函数的方案。 比如,我们可以订阅 ajax 请求的 error、succ 等事件。或者如果想在动画的每一帧完成之后做一 些事情,那我们可以订阅一个事件,然后在动画的每一帧完成之后发布这个事件。在异步编程中 使用发布—订阅模式,我们就无需过多关注对象在异步运行期间的内部状态,而只需要订阅感兴 趣的事件发生点。

第二点说明发布—订阅模式可以取代对象之间硬编码的通知机制,一个对象不用再显式地调 用另外一个对象的某个接口。发布—订阅模式让两个对象松耦合地联系在一起,虽然不太清楚彼 此的细节,但这不影响它们之间相互通信。当有新的订阅者出现时,发布者的代码不需要任何修 改;同样发布者需要改变时,也不会影响到之前的订阅者。只要之前约定的事件名没有变化,就 可以自由地改变它们。

六、命令模式

假设有一个快餐店,而我是该餐厅的点餐服务员,那么我一天的工作应该是这样的:当某位 客人点餐或者打来订餐电话后,我会把他的需求都写在清单上,然后交给厨房,客人不用关心是 哪些厨师帮他炒菜。我们餐厅还可以满足客人需要的定时服务,比如客人可能当前正在回家的路 上,要求 1 个小时后才开始炒他的菜,只要订单还在,厨师就不会忘记。客人也可以很方便地打 电话来撤销订单。另外如果有太多的客人点餐,厨房可以按照订单的顺序排队炒菜。

这些记录着订餐信息的清单,便是命令模式中的命令对象。

命令模式的用途

命令模式是最简单和优雅的模式之一,命令模式中的命令(command)指的是一个执行某些 特定事情的指令。

命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收 者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系。

拿订餐来说,客人需要向厨师发送请求,但是完全不知道这些厨师的名字和联系方式,也不 知道厨师炒菜的方式和步骤。命令模式把客人订餐的请求封装成 command 对象,也就是订餐中的 订单对象。这个对象可以在程序中被四处传递,就像订单可以从服务员手中传到厨师的手中。这 样一来,客人不需要知道厨师的名字,从而解开了请求调用者和请求接收者之间的耦合关系。

另外,相对于过程化的请求调用,command 对象拥有更长的生命周期。对象的生命周期是跟 初始请求无关的,因为这个请求已经被封装在了 command 对象的方法中,成为了这个对象的行为。 我们可以在程序运行的任意时刻去调用这个方法,就像厨师可以在客人预定 1 个小时之后才帮他 炒菜,相当于程序在 1 个小时之后才开始执行 command 对象的方法。

除了这两点之外,命令模式还支持撤销、排队等操作。

七、组合模式

我们知道地球和一些其他行星围绕着太阳旋转,也知道在一个原子中,有许多电子围绕着原 子核旋转。我曾经想象,我们的太阳系也许是一个更大世界里的一个原子,地球只是围绕着太阳 原子的一个电子。而我身上的每个原子又是一个星系,原子核就是这个星系中的恒星,电子是围 绕着恒星旋转的行星。一个电子中也许还包含了另一个宇宙,虽然这个宇宙还不能被显微镜看到, 但我相信它的存在。

也许这个想法有些异想天开,但在程序设计中,也有一些和“事物是由相似的子事物构成” 类似的思想。组合模式就是用小的子对象来构建更大的对象,而这些小的子对象本身也许是由更 小的“孙对象”构成的

组合模式的用途

组合模式将对象组合成树形结构,以表示“部分整体”的层次结构。 除了用来表示树形结 构之外,组合模式的另一个好处是通过对象的多态性表现,使得用户对单个对象和组合对象的使 用具有一致性,下面分别说明。

  • 表示树形结构。通过回顾上面的例子,我们很容易找到组合模式的一个优点:提供了一 种遍历树形结构的方案,通过调用组合对象的 execute 方法,程序会递归调用组合对象下 面的叶对象的 execute 方法,所以我们的万能遥控器只需要一次操作,便能依次完成关门、 打开电脑、登录 QQ 这几件事情。组合模式可以非常方便地描述对象部分整体层次结构。
  • 利用对象多态性统一对待组合对象和单个对象。利用对象的多态性表现,可以使客户端 忽略组合对象和单个对象的不同。在组合模式中,客户将统一地使用组合结构中的所有 对象,而不需要关心它究竟是组合对象还是单个对象。

这在实际开发中会给客户带来相当大的便利性,当我们往万能遥控器里面添加一个命令的时 候,并不关心这个命令是宏命令还是普通子命令。这点对于我们不重要,我们只需要确定它是一 个命令,并且这个命令拥有可执行的 execute 方法,那么这个命令就可以被添加进万能遥控器。

当宏命令和普通子命令接收到执行 execute 方法的请求时,宏命令和普通子命令都会做它们 各自认为正确的事情。这些差异是隐藏在客户背后的,在客户看来,这种透明性可以让我们非常 自由地扩展这个万能遥控器。

八、模板方法模式

在 JavaScript 开发中用到继承的场景其实并不是很多,很多时候我们都喜欢用 mix-in 的方式 给对象扩展属性。但这不代表继承在 JavaScript 里没有用武之地,虽然没有真正的类和继承机制, 但我们可以通过原型 prototype 来变相地实现继承。

模板方法模式的定义和组成

模板方法模式是一种只需使用继承就可以实现的非常简单的模式。

模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。通常 在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺 序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。

假如我们有一些平行的子类,各个子类之间有一些相同的行为,也有一些不同的行为。如果 相同和不同的行为都混合在各个子类的实现中,说明这些相同的行为会在各个子类中重复出现。 但实际上,相同的行为可以被搬移到另外一个单一的地方,模板方法模式就是为解决这个问题而 生的。在模板方法模式中,子类实现中的相同部分被上移到父类中,而将不同的部分留待子类来 实现。这也很好地体现了泛化的思想。

九、享元模式

享元(flyweight)模式是一种用于性能优化的模式,“fly”在这里是苍蝇的意思,意为蝇量 级。享元模式的核心是运用共享技术来有效支持大量细粒度的对象。

如果系统中因为创建了大量类似的对象而导致内存占用过高,享元模式就非常有用了。在 JavaScript 中,浏览器特别是移动端的浏览器分配的内存并不算多,如何节省内存就成了一件非 常有意义的事情。

享元模式的概念初听起来并不太好理解,所以在深入讲解之前,我们先看一个例子

十、职责链模式

职责链模式的定义是:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间 的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

职责链模式的名字非常形象,一系列可能会处理请求的对象被连接成一条链,请求在这些对 象之间依次传递,直到遇到一个可以处理它的对象,我们把这些对象称为链中的节点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BtxPMtpk-1644760209462)(设计模式.assets/image-20220211204911134.png)]

十一、中介者模式

在我们生活的世界中,每个人每个物体之间都会产生一些错综复杂的联系。在应用程序里也 是一样,程序由大大小小的单一对象组成,所有这些对象都按照某种关系和规则来通信。

平时我们大概能记住 10 个朋友的电话、30 家餐馆的位置。在程序里,也许一个对象会和其 他 10 个对象打交道,所以它会保持 10 个对象的引用。当程序的规模增大,对象会越来越多,它 们之间的关系也越来越复杂,难免会形成网状的交叉引用。当我们改变或删除其中一个对象的时 候,很可能需要通知所有引用到它的对象。这样一来,就像在心脏旁边拆掉一根毛细血管一般, 即使一点很小的修改也必须小心翼翼,如图 14-1 所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G1d9TeOJ-1644760209463)(设计模式.assets/image-20220211205704103.png)]

面向对象设计鼓励将行为分布到各个对象中,把对象划分成更小的粒度,有助于增强对象的 可复用性,但由于这些细粒度对象之间的联系激增,又有可能会反过来降低它们的可复用性。

中介者模式的作用就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的 相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知 中介者对象即可。中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。中介者 图灵社区会员 轩辕 专享 尊重版权 190 第 14 章 中介者模式 模式使网状的多对多关系变成了相对简单的一对多关系,如图 14-2 所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mZ6txQhv-1644760209464)(设计模式.assets/image-20220211205746777.png)]

如果对象 A 发生了改变,则需要同时通知跟 A 发生引用关系的 B、D、E、F 这 4 个对象;而在图 14-2 中,使用中介者模式改进之后,A 发生改变时则只需要通知这个中介者 对象即可。

十二、装饰者模式

我们玩魔兽争霸的任务关时,对 15 级乱加技能点的野生英雄普遍没有好感,而是喜欢留着 技能点,在游戏的进行过程中按需加技能。同样,在程序开发中,许多时候都并不希望某个类天 生就非常庞大,一次性包含许多职责。那么我们就可以使用装饰者模式。装饰者模式可以动态地 给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象。

在传统的面向对象语言中,给对象添加功能常常使用继承的方式,但是继承的方式并不灵活, 还会带来许多问题:一方面会导致超类和子类之间存在强耦合性,当超类改变时,子类也会随之 改变;另一方面,继承这种功能复用方式通常被称为“白箱复用”,“白箱”是相对可见性而言的, 在继承方式中,超类的内部细节是对子类可见的,继承常常被认为破坏了封装性。

使用继承还会带来另外一个问题,在完成一些功能复用的同时,有可能创建出大量的子类, 使子类的数量呈爆炸性增长。比如现在有 4 种型号的自行车,我们为每种自行车都定义了一个单 独的类。现在要给每种自行车都装上前灯、尾 灯和铃铛这 3 种配件。如果使用继承的方式来给 每种自行车创建子类,则需要 4×3 = 12 个子类。 但是如果把前灯、尾灯、铃铛这些对象动态组 合到自行车上面,则只需要额外增加 3 个类。

这种给对象动态地增加职责的方式称为装 饰者(decorator)模式。装饰者模式能够在不改 变对象自身的基础上,在程序运行期间给对象 动态地添加职责。跟继承相比,装饰者是一种 更轻便灵活的做法,这是一种“即用即付”的 方式,比如天冷了就多穿一件外套,需要飞行 时就在头上插一支竹蜻蜓,遇到一堆食尸鬼时 就点开 AOE(范围攻击)技能。

十三、状态模式

状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变。

状态模式的定义

通过电灯的例子,相信我们对于状态模式已经有了一定程度的了解。

现在回头来看 GoF 中 对状态模式的定义:

允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。

我们以逗号分割,把这句话分为两部分来看。第一部分的意思是将状态封装成独立的类,并 将请求委托给当前的状态对象,当对象的内部状态改变时,会带来不同的行为变化。电灯的例子 足以说明这一点,在 off 和 on 这两种不同的状态下,我们点击同一个按钮,得到的行为反馈是截 然不同的。

第二部分是从客户的角度来看,我们使用的对象,在不同的状态下具有截然不同的行为,这 个对象看起来是从不同的类中实例化而来的,实际上这是使用了委托的效果。

十四、适配器模式

适配器模式的作用是解决两个软件实体间的接口不兼容的问题。使用适配器模式之后,原本 由于接口不兼容而不能工作的两个软件实体可以一起工作。

适配器的别名是包装器(wrapper),这是一个相对简单的模式。在程序开发中有许多这样的 场景:当我们试图调用模块或者对象的某个接口时,却发现这个接口的格式并不符合目前的需求。 这时候有两种解决办法,第一种是修改原来的接口实现,但如果原来的模块很复杂,或者我们拿 到的模块是一段别人编写的经过压缩的代码,修改原接口就显得不太现实了。第二种办法是创建 一个适配器,将原接口转换为客户希望的另一个接口,客户只需要和适配器打交道。

适配器模式是一对相对简单的模式。在本书提到的设计模式中,有一些模式跟适配器模式的 结构非常相似,比如装饰者模式、代理模式和外观模式(参见第 19 章)。这几种模式都属于“包 装模式”,都是由一个对象来包装另一个对象。区别它们的关键仍然是模式的意图。

  • 适配器模式主要用来解决两个已有接口之间不匹配的问题,它不考虑这些接口是怎样实 现的,也不考虑它们将来可能会如何演化。适配器模式不需要改变已有的接口,就能够 使它们协同作用。
  • 装饰者模式和代理模式也不会改变原有对象的接口,但装饰者模式的作用是为了给对象 增加功能。装饰者模式常常形成一条长的装饰链,而适配器模式通常只包装一次。代理 模式是为了控制对对象的访问,通常也只包装一次。
  • 外观模式的作用倒是和适配器比较相似,有人把外观模式看成一组对象的适配器,但外 观模式最显著的特点是定义了一个新的接口。

就能够 使它们协同作用。

  • 装饰者模式和代理模式也不会改变原有对象的接口,但装饰者模式的作用是为了给对象 增加功能。装饰者模式常常形成一条长的装饰链,而适配器模式通常只包装一次。代理 模式是为了控制对对象的访问,通常也只包装一次。
  • 外观模式的作用倒是和适配器比较相似,有人把外观模式看成一组对象的适配器,但外 观模式最显著的特点是定义了一个新的接口。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值