第十九章 最少知识原则

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

这里的软体是一个广义的概念,不仅包括对象,还包括系统、模块、函数、变量等。

19.1 减少对象之间的联系

单一职责原则指导我们把对象划分成较小的粒度,这可以提高对象的可复用性。但越来越多的对象之间可能会产生错综复杂的联系,如果修改了其中一个对象,很可能会影响到跟它相互引用的其他对象。对象和对象耦合在一起,有可能会降低它们的可复用性。

最少知识原则要求我们在设计程序时,应当尽量减少对象之间的交互。如果两个对象之间不必彼此直接通信,那么这两个对象就不要发生直接的相互联系。常见的做法是引入一个第三者对象,来承担这些对象之间的通信作用。如果一些对象需要向另一些对象发起请求,可以通过第三者对象来转发这些请求。

19.3 封装在最少知识原则的体现

封装在很大程度上表达的是数据的隐藏。一个模块或者对象可以将内部的数据或者实现细节隐藏起来,只暴露必要的接口 API 供外界访问。对接之间难免产生联系,当一个对象必须引用另外一个对象的时候,我们可以让对象只暴露必要的接口,让对象之间的联系限制在最小范围之内。

同时,封装也用来限制变量的作用域。

在 JavaScript 中对变量作用域的规定是:

❏ 变量在全局声明,或者在代码的任何位置隐式申明(不用 var),则该变量在全局可见;

❏ 变量在函数内显式申明(使用 var),则在函数内可见。把变量的可见性限制在一个尽可能小的范围内,这个变量对其他不相关模块的影响就越小,变量被改写和发生冲突的机会也越小。这也是广义的最少知识原则的一种体现。

假设我们要编写一个具有缓存效果的计算乘积的函数 function mult (){},我们需要一个对象 var cache = {}来保存已经计算过的结果。cache 对象显然只对 mult 有用,把 cache 对象放在 mult 形成的闭包中,显然比把它放在全局作用域更加合适,

js
1var mult = (function () {
2  var cache = {}
3  return function () {
4    var args = Array.prototype.join.call(arguments, ', ')
5    if (cache[args]) {
6      return cache[args]
7    }
8    var a = 1
9    for (var i = 0, l = arguments.length; i < l; i++) {
10      a = a * arguments[i]
11    }
12    return (cache[args] = a)
13  }
14})()
15mult(1, 2, 3) // 输出: 6

最少知识原则也叫迪米特法则(Law of Demeter, LoD), “迪米特”这个名字源自 1987 年美国东北大学一个名为“Demeter”的研究项目。许多人更倾向于使用迪米特法则这个名字,也许是因为显得更酷一点。

一是因为这个名字更能体现其含义,另一个原因是“法则”给人的感觉是必须强制遵守,而原则只是一种指导,没有哪条原则是在实际开发中必须遵守的。比如,虽然遵守最小知识原则减少了对象之间的依赖,但也有可能增加一些庞大到难以维护的第三者对象。跟单一职责原则一样,在实际开发中,是否选择让代码符合最少知识原则,要根据具体的环境来定。