跳到主要内容

《JavaScript 设计模式与开发实践》

前言

  • 设计模式的定义

在软件设计过程中针对特定问题的简洁而优雅的解决方案

第 1 章 面向对象的 JavaScript

  • 1.1

    • 鸭子类型

    • 面向接口编程

  • 1.2 多态

    • 同一个操作作用于不同对象时可以产生不同的效果

    • 使用继承来实现多态效果

  • 1.3 封装

    • 封装的目的是将信息隐藏

    • 封装数据/封装实现/封装类型/封装变化

  • 1.4 原型模式和基于原型继承的 JavaScript 对象系统

    • 原型模式不仅仅是一种设计模式,也是一种编程范式

    • 原型编程范式至少包括以下基本规则

      • 所有的数据都是对象

      • 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它

      • 对象会记住它的原型

      • 如果对象无法响应某个请求,它会把这个请求委托给它的原型

第 2 章 this、call 和 apply

  • this 的指向常见的四种情况

    • 作为对象的方法调用

    • 作为普通函数调用

    • 构造器调用

    • Function.prototype.call 或 Function.prototype.apply 调用

第 3 章 闭包和高阶函数

  • 虽然 JavaScript 是一门完整的面向对象的编程语言,但这门语言同时也拥有许多函数式语言的特性。

  • 命令模式上的意图是把请求封装为对象,从而分离请求的发起者和请求的接受者之间的耦合关系

  • 高阶函数是指至少满足以下条件之一的函数

    • 函数可以作为参数被传递

    • 函数可以作为返回值输出

  • AOP(面向切面编程)主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,再通过动态植入的方式参入业务逻辑模块中。可 以保持业务逻辑模块的纯净和高内聚,方便的复用日志统计等功能模块

  • 函数柯里化

    • currying 又称部分求值。可多次接受部分参数

    • uncurrying 拆分方法依赖,让方法可以脱离对象独立使用

    • 函数节流 减少函数调用次数

    • 分时函数 将批量任务分时间段处理

    • 惰性加载函数 在第一次执行时判断情况重写函数,减少后续执行成本

第 4 章 单例模式

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

  • 代理实现单例:代理中判断是否创建并返回单例

  • JavaScript 单模块、全局变量等就可实现

  • 惰性单例:使用时再去创建

第 5 章 策略模式

  • 定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换

  • 使用策略模式:拆分多个函数,调用时根据情况调用不同函数

  • 表单校验中的策略模式

  • 策略模式的优缺点

    • 优点

      • 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。

      • 策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的 strategy 中,使得它们易于切换,易于理解,易于扩 展。

      • 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。

      • 在策略模式中利用组合和委托来让 Context 拥有执行算法的能力,这也是继承的一种更轻便的替代方案。

    • 缺点

      • 会在程序中增加许多策略类或者策略对象

      • 必须了解所有的 strategy,必须了解各个 strategy 之间的不同点

  • 函数作为一等对象的语言中,策略模式是隐形的

    • 可以使用函数参数简单的实现策略选择

第 6 章 代理模式

  • 保护代理和虚拟代理

  • 代理的意义 单一职责原则

  • 代理和本体接口的一致性

    • 用户可以放心地请求代理,他只关心是否能得到想要的结果。

    • 在任何使用本体的地方都可以替换成使用代理。

  • 虚拟代理合并 HTTP 请求

  • 虚拟代理在惰性加载中的应用

  • 缓存代理

  • 其它代理模式

    • 防火墙代理:控制网络资源的访问,保护主体不让“坏人”接近

    • 远程代理:为一个对象在不同的地址空间提供局部代表

    • 保护代理:用于对象应该有不同访问权限的情况

    • 智能引用代理:取代了简单的指针,它在访问对象时执行一些附加操作,比如计算一个对象被引用的次数

    • 写时复制代理:通常用于复制一个庞大对象的情况。写时复制代理延迟了复制的过程,当对象被真正修改时,才对它进行复制操 作

第 7 章 迭代器模式

  • 定义:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示

  • 内部迭代器:定义好迭代规则,方便但不自由

  • 外部迭代器:显式地请求迭代下一个元素,增加了复杂度但增强了灵活性

  • 倒序迭代器

  • 中止迭代器 可提前中止

第 8 章 发布-订阅模式

  • 定义:又叫观察者模式,定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知

  • 自定义事件

第 9 章 命令模式

  • 命令模式的由来,其实是回调(callback)函数的一个面向对象的替代品。

  • 命令队列:记录命令执行的历史,方便撤销、重做等

  • 宏命令:一组命令的集合,可以一次执行一批命令

  • 智能命令与傻瓜命令:智能命令可以直接实现请求,就不再需要接收者的存在

第 10 章 组合模式

第 11 章 模版方法模式

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

  • JavaScript 没有抽象类的缺点和解决方案

    • 鸭子类型检测

    • 继承并在父类抛出异常

  • 钩子方法

    • 在容易变化的地方放置钩子

    • 可使用钩子方法的返回结果决定了模版方法后面的执行步骤

  • 好莱坞原则

第 12 章 享元模式

  • 享元模式的核心是运用共享技术来有效支持大量细粒度的对象

  • 内部状态与外部状态

    • 内部状态存储于对象内部

    • 内部状态可以被一些对象共享

    • 内部状态独立于具体的场景,通常不会改变

    • 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享

第 13 章 职责链模式

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

  • 优缺点

    • 优点:解耦了请求发送者和 N 个接收者之间的复杂关系

    • 缺点:无法保证被处理;长链会存在损耗

第 14 章 中介者模式

  • 中介者模式是迎合迪米特法则的一种实现。迪米特法则也叫最少知识原则,是指一个对象应该尽可能少地了解另外的对象。方便对象 间解耦。

  • 中介者对象经常比较巨大且难以维护

第 15 章 装饰者模式

  • 装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。

  • 代理模式和装饰者模式最重要的区别在于它们的意图和设计目的。

    • 代理模式的目的是,当直接访问本体不方便或者不符合需要时,为这个本体提供一个替代者。本体定义了关键功能,而代理提供 或拒绝对它的访问,或者在访问本体之前做一些额外的事情。

    • 装饰者模式的作用就是为对象动态加入行为。

第 16 章 状态模式

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

  • 优缺点

    • 优点

      • 状态模式定义了状态与行为之间的关系,并将它们封装在一个类里。通过增加新的状态类,很容易增加新的状态和转换。

      • 避免 Context 无限膨胀,状态切换的逻辑被分布在状态类中,也去掉了 Context 中原本过多的条件分支

      • 用对象代替字符串来记录当前状态,使得状态的切换更加一目了然。

      • Context 中的请求动作和状态类中封装的行为可以非常容易地独立变化而互不影响。

    • 缺点

      • 会在系统中定义许多状态类,编写 20 个状态类是一项枯燥乏味的工作,而且系统中会因此而增加不少对象

      • 由于逻辑分散在状态类中,虽然避开了不受欢迎的条件分支语句,但也造成了逻辑分散的问题,我们无法在一个地方就看出 整个状态机的逻辑

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

    • 策略模式和状态模式的相同点是,它们都有一个上下文、一些策略或者状态类,上下文把请求委托给这些类来执行。

    • 它们之间的区别是策略模式中的各个策略类之间是平等又平行的,它们之间没有任何联系,所以客户必须熟知这些策略类的作用 ,以便客户可以随时主动切换算法;而在状态模式中,状态和状态对应的行为是早已被封装好的,状态之间的切换也早被规定完 成,“改变行为”这件事情发生在状态模式内部。对客户来说,并不需要了解这些细节。这正是状态模式的作用所在。

  • 表驱动的有限状态机

    • 其实还有另外一种实现状态机的方法,这种方法的核心是基于表驱动的。我们可以在表中很清楚地看到下一个状态是由当前状态 和行为共同决定的。这样一来,我们就可以在表中查找状态,而不必定义很多条件分支

第 17 章 适配器模式

  • 适配器模式的作用是解决两个软件实体间的接口不兼容的问题。

  • 适配器的别名是包装器(wrapper),这是一个相对简单的模式

  • 适配器模式的意图和区分

    • 适配器模式主要用来解决两个已有接口之间不匹配的问题,它不考虑这些接口是怎样实现的,也不考虑它们将来可能会如何演化 。适配器模式不需要改变已有的接口,就能够使它们协同作用

    • 装饰者模式和代理模式也不会改变原有对象的接口,但装饰者模式的作用是为了给对象增加功能。装饰者模式常常形成一条长的 装饰链,而适配器模式通常只包装一次。代理模式是为了控制对对象的访问,通常也只包装一次

    • 外观模式的作用倒是和适配器比较相似,有人把外观模式看成一组对象的适配器,但外观模式最显著的特点是定义了一个新的接 口

第 18 章 单一职责原则

  • 单一职责原则(SRP)原则体现为:一个对象(方法)只做一件事情

  • 优缺点

    • 优点:降低了单个类或者对象的复杂度,按照职责把对象分解成更小的粒度,有助于代码复用和单元测试,单一职责变更时不会 影响其它职责

    • 缺点:增加编写代码的复杂度,增加了对象间项目联系的难度

第 19 章 最少知识原则

  • 最少知识原则(LKP)说的是一个软件实体应当尽可能少地与其他实体发生相互作用

  • 外观模式

    • 外观模式主要是为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使子系统更加容易使用
  • 最少知识原则也叫迪米特法则(Law of Demeter,LoD)

  • 虽然遵守最小知识原则减少了对象之间的依赖,但也有可能增加一些庞大到难以维护的第三者对象

第 20 章 开闭原则

  • 开放-封闭原则(OCP):软件实体(类、模块、函数)等应该是可以扩展的,但是不可修改。

  • 用对象的多态性消除条件分支

  • 开放-封闭原则的相对性

    • 挑选出最容易发生变化的地方,然后构造抽象来封闭这些变化。

    • 在不可避免发生修改的时候,尽量修改那些相对容易修改的地方。拿一个开源库来说,修改它提供的配置文件,总比修改它的源 代码来得简单。

第 21 章 接口和面向接口编程

  • 面向抽象编程

第 22 章 代码重构

  • 模式和重构之间有着一种与生俱来的关系。从某种角度来看,设计模式的目的就是为许多重构行为提供目标。

  • 提炼函数

    • 避免出现超大函数。

    • 独立出来的函数有助于代码复用。

    • 独立出来的函数更容易被覆写。

    • 独立出来的函数如果拥有一个良好的命名,它本身就起到了注释的作用。

  • 合并重复的条件片段

  • 把条件分支语句提炼成函数

  • 合理使用循环

  • 提前让函数退出代替嵌套条件分支

  • 传递对象参数代替过长的参数列表

  • 尽量减少参数数量

  • 少用三目运算符

  • 合理使用链式调用

  • 分解大型类

  • 用 return 退出多重循环