第十九章 最少知识原则
最少知识原则(LKP)说的是一个软件实体应当尽可能少地与其他实体发生相互作用。
这里的软体是一个广义的概念,不仅包括对象,还包括系统、模块、函数、变量等。
19.1 减少对象之间的联系
单一职责原则指导我们把对象划分成较小的粒度,这可以提高对象的可复用性。但越来越多的对象之间可能会产生错综复杂的联系,如果修改了其中一个对象,很可能会影响到跟它相互引用的其他对象。对象和对象耦合在一起,有可能会降低它们的可复用性。
最少知识原则要求我们在设计程序时,应当尽量减少对象之间的交互。如果两个对象之间不必彼此直接通信,那么这两个对象就不要发生直接的相互联系。常见的做法是引入一个第三者对象,来承担这些对象之间的通信作用。如果一些对象需要向另一些对象发起请求,可以通过第三者对象来转发这些请求。
19.3 封装在最少知识原则的体现
封装在很大程度上表达的是数据的隐藏。一个模块或者对象可以将内部的数据或者实现细节隐藏起来,只暴露必要的接口 API 供外界访问。对接之间难免产生联系,当一个对象必须引用另外一个对象的时候,我们可以让对象只暴露必要的接口,让对象之间的联系限制在最小范围之内。
同时,封装也用来限制变量的作用域。
在 JavaScript 中对变量作用域的规定是:
❏ 变量在全局声明,或者在代码的任何位置隐式申明(不用 var),则该变量在全局可见;
❏ 变量在函数内显式申明(使用 var),则在函数内可见。把变量的可见性限制在一个尽可能小的范围内,这个变量对其他不相关模块的影响就越小,变量被改写和发生冲突的机会也越小。这也是广义的最少知识原则的一种体现。
假设我们要编写一个具有缓存效果的计算乘积的函数 function mult (){},我们需要一个对象 var cache = {}来保存已经计算过的结果。cache 对象显然只对 mult 有用,把 cache 对象放在 mult 形成的闭包中,显然比把它放在全局作用域更加合适,
js1var 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”的研究项目。许多人更倾向于使用迪米特法则这个名字,也许是因为显得更酷一点。
一是因为这个名字更能体现其含义,另一个原因是“法则”给人的感觉是必须强制遵守,而原则只是一种指导,没有哪条原则是在实际开发中必须遵守的。比如,虽然遵守最小知识原则减少了对象之间的依赖,但也有可能增加一些庞大到难以维护的第三者对象。跟单一职责原则一样,在实际开发中,是否选择让代码符合最少知识原则,要根据具体的环境来定。