初识Javascript 设计模式

最近在看js设计模式的书

设计模式分为三大类:

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
结构型模式,共七种:适配器模式、装饰者模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
还有两类:并发型模式和线程池模式。

当然js中常用的没有那么多,在《JavaScript设计模式与开发实践》 这本书中,作者详细介绍了14种开发中常用的设计模式

创建型模式:

  • 常用一种:单例模式。
  • 不常用四种:(工厂方法模式、抽象工厂模式、建造者模式、原型模式

结构型模式:

  • 常用五种:代理模式、组合模式、享元模式、装饰者模式、适配器模式。
  • 不常用二种(外观模式、桥接模式、)

行为型模式:

  • 常用八种:策略模式、迭代子模式、观察者模式、命令模式、模板方法模式、责任链模式、中介者模式、状态模式。
  • 不常用三种(备忘录模式、访问者模式、解释器模式。)
    还有两类:并发型模式和线程池模式。js中也一般不用(也许node中可能用到)

14种js常用设计模式简单了解

①—单列模式

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

典型的单列模式:
单列模式已经融入到语言当中,只是我们不知道而已
在全局作用域中声明一个变量 a 就是一种单列模式

var a = 2 // js中没有真正的类,所以一个全局变量就是一个单一的实例,也可以全局访问它

但是全局变量有很多的问题,它很容易造成命名空间污染,在中大型项目,变量很可能被别人覆盖,造成程序错误,js语言的创造者 Brendan Eich 在访谈中也承认 全局变量是设计上的失误。
应该使用以下办法来尽量减少全局变量的使用

  1. 使用命名空间
  2. 使用闭包封装私有变量
使用惰性单列技术的是js中单列模式的重点

②—策略模式

定义:定义一系列的算法,把他们一个个封装起来,并且使他们有交互性。策略模式使得算法在用户使用的时候能独立的改变。
典型的策略模式:

  1. 封装表单验证函数,当用户焦点离开表单元素之后自动验证。
  2. 封装动画函数animate(node, target),填入元素和目的地。

js中 除了类封装算法和行为之外,使用函数也是一种选择,这些“算法”可以被封装到函数中并且四处传递,也就说我们常说的“高阶函数”,在js这种将函数作为一等对象的语言里,策略模式以及融入到了语言当中

③—代理模式

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

  1. 虚拟代理
  2. 缓存代理

④迭代器模式

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

  1. jQuery 中的 $.each
  2. js 中 Array.prototype.forEach
  3. 自己实现一个迭代器模式
var each = function (ary,  callback)  {
  for (var i = 0;  i < ary.length; i++) {
      callback.call(ary[i], i, ary[i])
  }
}
each( [1, 2, 3], function(1,  n) { 
    alert( [ i, n]
  })

总结:迭代器模式是一种相对简单的模式,简单到很多时候我们都不认为它是一种设计模式,目前绝大多数语言都内置迭代器。

⑤—发布-订阅模式(观察者模式)

定义:他定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时候,虽有依赖它的对象都将得到通知。在js开发中,我们一般用事件模型来替代传统的发布-订阅模式
一、典型的发布-订阅模式:

  1. DOM 提供的事件监听方法就是一种内置发布订阅模式,box.addEventListener('click', function () { ...do something}, 用户的鼠标点击事件,就是发布者,浏览器监听用户的点击行为,就是订阅者
  2. 自定义事件:实际开发中,很多模块都依赖于用户是否登录,当用户登录成功,就发布登录成功的消息,其他关系用户是否登录的模块只需要监听用户是否登录成功的消息即可

二、取消订阅事件
三、全局的发布-订阅对象

  • 在全局发布-订阅模式中,需要注意模块间的通信,全局发布-订阅模式用的太多了,会对维护带来一些麻烦
    四、先发布后订阅可以吗?
    小结:
    发布-订阅模式在实际开发中非常有用,他的优点非常明显,一为时间上的解耦,二为对象间的解耦,他的应用非常广泛,既可以用在异步编程当中,也可以帮助我们完成更松耦合的代码编写。发布订阅模式还可以用来帮助实现一些别的设计模式,比如中介者模式。从架构上看,无论是MVC 还是MVVM 都少不了发布-订阅模式的参与,而且js也是一门基于事件驱动的语言**

⑥—命令模式

定义:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。
JS作为将函数作为一等对象的语言,跟策略模式一样,命令模式也早已融入到了js的语言当中。
典型的命令模式:

  1. 命令模式一般需要完成撤销



  
  
  Document


  
  


小结:
和许多其他语言不同,js可以用高阶函数非常方便地实现命令模式。命令模式在js语言中是一种隐形的模式,没有接收者的智能命令,退化到和策略模式非常相近,从代码结构上已经无法分辨他们,能分辨的只有他们意图的不同,策略模式指向的问题域更小,所有策略对象的目标总是一致的,他们只是达到这个目标的手段不同,他们的内部实现是针对“算法”而言的。而智能命令模式指向的问题域更广,command对象解决的目标更具有发散性。命令模式还可以完成撤销、排队等功能。

⑦—组合模式

定义:把多个对象组成树状结构来表示局部与整体,这样用户可以一样的对待单个对象和对象的组合。
典型的组合模式:

  1. 现实中的万能遥控器。
  2. 操作系统中的扫描文件夹功能。
    组合模式可以让我们使用树形方式创建对象的结构。我们可以把相同的操作应用在组合对象和单个对象上;在大多数情况下,我们都可以忽略掉组合对象之间的差别,从而用一致的方式来处理他们。

⑧—模板方法模式

定义:模板方法模式准备一个抽象类,将部分逻辑以具体方法及具体构造子类的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。先构建一个顶级逻辑框架,而将逻辑的细节留给具体的子类去实现
典型的模板方法模式:

  1. 现实中泡咖啡和茶。
  2. 在java中 模板方法模式常被架构师用于搭建项目的框架,架构师定好了框架的骨架,程序员继承框架的结构之后,负责往里面填空。
    在javascript中 我们很多时候不需要依样画瓢地去实现一个模板方法,高阶函数是更好的选择。

⑨—享元模式

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

  1. 一个内衣工厂,有50种男款内衣,50种女款内衣,他们需要1个男模特和一个女模特来拍宣传照片。例子中2个模特是内部状态,100种内衣是外部状态
  2. 微云的上传模块功能,需要上传200乃至1000个文件的时候:一个程序中使用了大量的相似对象,造成很大的内存开销,这些对象的大多数状态都可以变为外部状态,剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象。
    对象池是另外一种性能优化方案,它跟享元模式有一些相似之处,但没有分离内部状态和外部状态这个过程。
    享元模式是为了解决性能问题而生的模式,这个跟大部分模式的诞生原因都不一样,在一个存在大量相似对象的系统中,享元模式可以很好地解决大量对象带来的性能问题。

⑩—职责链模式

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

  1. 现实中:公交车上挤满了人,找不到售票员,我们不需要知道售票员在哪里,只需要知道往哪个方向传递就好了,职责链模式可以弱化发送至和接收者的强联系,如果不使用职责链,我们就得先搞清楚谁是售票员,才能把硬币给他。
  2. 在一个商城的项目中,我们把一个订单传递给一个第一个节点,如果第一个节点处理不了该订单,他会自动调用下一个节点,知道有一个节点可以处理他
  3. promise 解决回调地狱函数也是一种异步职责链模式。

在javascript开发中,职责链模式是最容易被忽视的模式之一。实际上只要运用得当,职责链模式可以很好的帮助我们管理代码,降低发起请求的对象和处理请求的对象之间的耦合性。职责链中的节点数量和顺序是可以自由变化的,我们可以在运行时决定链条中包含哪些节点。
无论是作用域链、原型链、还是DOM节点中的事件冒泡,我们都能从中找到职责链模式的影子,职责链模式还可以和组合模式结合在一起,用来连接部件和父部件,或是提高组合对象的效率。

①①—中介者模式

定义:包装了一系列对象相互作用的方式,使得这些对象不必相互明显作用,从而使它们可以松散偶合。当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用,保证这些作用可以彼此独立的变化。
典型的中介者模式:

  1. 现实中的机场指挥塔,假如有100架飞机在天上,他们之间需要通讯,如果没有指挥塔,就不能及时作出起飞,降落,改变航线等决定,后果不可设想。指挥塔作为调停者,知道每一架飞机的飞行状况,所以他可以安排所有飞机的起降时间,及时作出航线调整。 博彩公司也是:世界杯期间购买足球彩票,无论输赢,钱都是给博彩公司就行。
  2. 开发中商城项目中 ,一个产品有许多选项,比如电脑:比如颜色 CPU型号 显示屏尺寸,购买数量,当前库存等,如果没有中介者,这些节点对象都是强耦合在一起的。后期如果要修改一些设置,对维护带来非常大的困难。
    特性:
    中介者模式是迎合迪米特法则的一种实现。迪米特法则也叫最少知识原则,是指一个对象应该尽可能少地了解另外的对象。如果对象之间的耦合性太高,一个对象发生改变之后,难免会影响到其他的对象,跟‘城们失火,殃及池鱼’的道理是一样的。而在中介则模式里,对象之间几乎不知道彼此的存在,他们只能通过中介者对象互相影响对方。
    缺点:
    最大的缺点是系统中会新增一个中介者对象,因为对象之间的交互的复杂性,转移成了中介者对象的复杂性,使得中介者对象经常是巨大的。中介者对象自身往往就是一个难以维护的对象。

①②—装饰者模式

定义:向某个对象动态地添加更多的功能,而不会从这个类中派生的其他对象。装饰模式是除类继承外另一种扩展功能的方法。
典型的装饰者模式:

  1. 魔兽争霸中的15级英雄,但是不点技能,需要的时候动态的点上需要的技能
  2. js中 不需要使用类来实现装饰者模式,javascript是一门动态语言,改变对象相当容易,我们可以直接改写对象或者对象的某个方法。



  
  
  Document


  


不过这种方法有2个缺点:

  1. 必须维护_onload这个中间变量
  2. 遇到了this被劫持的问题

所以需要使用AOP装饰函数来完美解决这个问题

AOP:

①③—状态模式

定义:让一个对象在其内部状态改变的时候,其行为也随之改变。状态模式需要对每一个系统可能取得的状态创立一个状态类的子类。当系统的状态变化时,系统便改变所选的子类。

典型的状态模式:

  1. 现实生活中:用开关控制灯的开关,按第一下开灯,按第二下关灯,重复的按开关的行为,但是灯的状态是不一样的。复杂一点的灯按第一下强光,第二下变弱光,第三下会关闭,第四下又会变成强光,循环往复。
  2. 实际开发中:很多场景都有状态机的应用场景,一个下拉菜单在hover 动作下有显示、悬浮、隐藏等状态;一次TCP请求有建立连接、监听、关闭等状态;一个格斗游戏中人物有攻击、防御、跳跃、跌倒等状态。

状态模式和策略模式的关系

看起来状态模式和策略模式非常相似,难以区分它们。实际上是的,它们就像一对儿双胞胎。策略模式和和状态模式的相同点是,它们都有一个上下文、一些策略或者状态类,上下文把请求委托给这些类来执行。
它们之间的区别是策略模式中的各个策略类之间是平等又平行的,它们之间没有任何联系,所以客户必须熟知这些策略的作用,以便客户可以随时主动切换算法;而在状态模式中,状态和状态对应的行为是早已经被封装好的,状态之间的切换也早被规定完成,“改变行为”这件事情发生在状态模式内部。对客户来说,并不需要了解这些细节。这正是状态模式的作用所在。

小结:

状态模式也许是被大家低估的模式之一。实际上,通过状态模式重构代码之后,很多杂乱无章的代码会变得清晰。虽然状态模式一开始不是非常容易理解,但是我们有必要去好好掌握这种设计模式。

①④—适配器模式

定义:适配器也叫包装器wrapper,将某个类的接口转换成客户端期望的另一个接口表示。适配器模式可以消除由于接口不匹配所造成的类兼容性问题。
典型的适配器模式:

  1. 现实中的适配器:
  • 港式插头转换器:如果从香港买了一个Mac book,会发现充电器无法插在家里的插座上,为此而改造家里的插座显然不方便,所以我们需要一个适配器。
  • 电源适配器:中国大陆的日常生活电交流电一般是220V。日本和韩国的交流电压大多是100v,而英国和澳大利亚的是240v。笔记本电脑的电源适配器就承担了转换电压的作用,电源适配器使笔记本电脑在100v-240v的电压之内都能正常工作,这也是它为什么被称为“适配器”的原因
  • USB转接口:旧电脑上PS2接口是连接鼠标、键盘等其他外部设备的标准接口,但是随着技术的发展,越来越多的电脑开始放弃了PS2接口,转而仅支持USB 接口。所以那些只有PS2接口的鼠标、 键盘、游戏手柄等,需要一个USB转接口才能继续正常工作。
  1. 实际开发中的适配器:
    比如百度地图 谷歌地图,高德地图等,接口不一致的时候,如果开发时候并没有搞清楚,那使用适配器模式可以解决这个问题。

  1. 还有比如JSON 流行之前很多cgi返回的都是XML 格式的数据,如果今天仍然想继续使用这些接口,显然我们可以创造一个XML-JSON 的适配器

适配器的应用:
如果现有的接口已经能够正常工作,那我们就永远也不会用上适配器模式。适配器模式是一种“亡羊补牢”的模式,没人会在程序设计之初就用上它,因为没人会预料到未来的事情,也许现在接口工作的很好,未来的某天却不再适用与新系统,那么我们可以用适配器模式把旧接口包装成一个新的接口,使他继续保持生命力。 XML-JSON 适配器就是如此。
装饰者模式、代理模式、外观模式 和适配器模式
装饰者模式、代理模式、外观模式 和适配器模式他们的结构非常相似,都属于“包装模式”,都是由一个对象来包装另一个对象。区别他们的关键还是模式的意图。

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

小结:适配器模式是一种相对简单的模式。如果现有的接口已经能够正常使用,那我们就不会用上适配器模式。

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