JS最少知识原则
夏安 人气:01. 减少对象之间的联系
单一职责原则指导我们把对象划分成较小的粒度,这可以提高对象的可复用性。但越来越多的对象之间可能会产生错综复杂的联系,如果修改了其中一个对象,很可能会影响到跟它相互引用的其他对象。对象和对象耦合在一起,有可能会降低它们的可复用性。在程序中,对象的“朋友”太多并不是一件好事,“城门失火,殃及池鱼”和“一人犯法,株连九族”的故事时有发生。
最少知识原则要求我们在设计程序时,应当尽量减少对象之间的交互。如果两个对象之间不必彼此直接通信,那么这两个对象就不要发生直接的相互联系。常见的做法是引入一个第三者对象,来承担这些对象之间的通信作用。如果一些对象需要向另一些对象发起请求,可以通过第三者对象来转发这些请求。
2. 设计模式中的最少知识原则
最少知识原则在设计模式中体现得最多的地方是中介者模式和外观模式,下面我们分别进行介绍。
中介者模式
在世界杯期间购买足球彩票,如果没有博彩公司作为中介,上千万的人一起计算赔率和输赢绝对是不可能的事情。博彩公司作为中介,每个人都只和博彩公司发生关联,博彩公司会根据所有人的投注情况计算好赔率,彩民们赢了钱就从博彩公司拿,输了钱就赔给博彩公司。
中介者模式很好地体现了最少知识原则。通过增加一个中介者对象,让所有的相关对象都通过中介者对象来通信,而不是互相引用。所以,当一个对象发生改变时,只需要通知中介者对象即可。
外观模式
外观模式在 JavaScript 中的使用场景并不多。外观模式主要是为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使子系统更加容易使用。
外观模式的作用是对客户屏蔽一组子系统的复杂性。外观模式对客户提供一个简单易用的高层接口,高层接口会把客户的请求转发给子系统来完成具体的功能实现。大多数客户都可以通过请求外观接口来达到访问子系统的目的。但在一段使用了外观模式的程序中,请求外观并不是强制的。如果外观不能满足客户的个性化需求,那么客户也可以选择越过外观来直接访问子系统。
拿全自动洗衣机的一键洗衣按钮举例,这个一键洗衣按钮就是一个外观。如果是老式洗衣机,客户要手动选择浸泡、洗衣、漂洗、脱水这 4 个步骤。如果这种洗衣机被淘汰了,新式洗衣机的漂洗方式发生了改变,那我们还得学习新的漂洗方式。而全自动洗衣机的好处很明显,不管洗衣机内部如何进化,客户要操作的,始终只是一个一键洗衣的按钮。这个按钮就是为一组子系统所创建的外观。但如果一键洗衣程序设定的默认漂洗时间是 20 分钟,而客户希望这个漂洗时间是 30 分钟,那么客户自然可以选择越过一键洗衣程序,自己手动来控制这些“子系统”运转。
外观模式容易跟普通的封装实现混淆。这两者都封装了一些事物,但外观模式的关键是定义一个高层接口去封装一组“子系统”。子系统在 C++或者 Java 中指的是一组类的集合,这些类相互协作可以组成系统中一个相对独立的部分。在 JavaScript 中我们通常不会过多地考虑“类”,如果将外观模式映射到 JavaScript 中,这个子系统至少应该指的是一组函数的集合。
最简单的外观模式应该是类似下面的代码:
const A = function () { a1(); a2(); } const B = function () { b1(); b2(); } const facade = function () { A(); B(); } facade();
现在再来看看外观模式和最少知识原则之间的关系。外观模式的作用主要有两点。
- 为一组子系统提供一个简单便利的访问入口。
- 隔离客户与复杂子系统之间的联系,客户不用去了解子系统的细节。
从第二点来,外观模式是符合最少知识原则的。比如全自动洗衣机的一键洗衣按钮,隔开了客户和浸泡、洗衣、漂洗、脱水这些子系统的直接联系,客户不用去了解这些子系统的具体实现。
假设我们在编写这个老式洗衣机的程序,客户至少要和浸泡、洗衣、漂洗、脱水这 4 个子系统打交道。如果其中的一个子系统发生了改变,那么客户的调用代码就得发生改变。而通过外观将客户和这些子系统隔开之后,如果修改子系统内部,只要外观不变,就不会影响客户的调用。同样,对外观的修改也不会影响到子系统,它们可以分别变化而互不影响。
3. 封装在最少知识原则中的体现
封装在很大程度上表达的是数据的隐藏。一个模块或者对象可以将内部的数据或者实现细节隐藏起来,只暴露必要的接口 API 供外界访问。对象之间难免产生联系,当一个对象必须引用另外一个对象的时候,我们可以让对象只暴露必要的接口,让对象之间的联系限制在最小的范围之内。
同时,封装也用来限制变量的作用域。在 JavaScript 中对变量作用域的规定是:
- 变量在全局声明,或者在代码的任何位置隐式申明(不用 var),则该变量在全局可见;
- 变量在函数内显式申明(使用 var),则在函数内可见。
把变量的可见性限制在一个尽可能小的范围内,这个变量对其他不相关模块的影响就越小,变量被改写和发生冲突的机会也越小。这也是广义的最少知识原则的一种体现。
假设我们要编写一个具有缓存效果的计算乘积的函数 function mult (){}
,我们需要一个对象 const cache = {}
来保存已经计算过的结果。cache
对象显然只对 mult
有用,把 cache
对象放在 mult
形成的闭包中,显然比把它放在全局作用域更加合适,代码如下:
const mult = (function () { const cache = {}; return function () { const args = Array.prototype.join.call(arguments, ','); if (cache[args]) { return cache[args]; } let a = 1; for (let i = 0, l = arguments.length; i < l; i++) { a = a * arguments[i]; } return cache[args] = a; } })(); mult(1, 2, 3); // 输出: 6
虽然遵守最小知识原则减少了对象之间的依赖,但也有可能增加一些庞大到难以维护的第三者对象。跟单一职责原则一样,在实际开发中,是否选择让代码符合最少知识原则,要根据具体的环境来定。
加载全部内容