设计原则之最少知识原则

参考资料

定义

最少知识原则(Least Knowledge Principle,LKP),又叫迪米特法则(Law of Demeter,LoD)。

最少知识原则的定义是:一个软件实体应当尽可能少地与其他实体发生相互作用。这里的软件实体是一个广义的概念,不仅包括对象,还包括系统、类、模块、函数、变量等。在应用中,可以引入第三方来管理实体之间的联系。

根本思想是:高内聚低耦合。强调了类之间的松耦合,类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不会对有关系的类造成波及。

使用场景:

  • 中介者模式;
  • 命令模式;
  • 外观模式;

现实中的LKP

  • 修电脑:我刚到公司上班,电脑坏了,需要技术部派人来给我修电脑,我不需要认识来给我修电脑的人是谁,我只需要认识技术部门的主管,具体他拍谁来给我修电脑,那是他的事。我的目的是赶紧把电脑修好。下面我们使用代码来实现这个小例子。
  • 明星与经纪人的关系实例:明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如与粉丝的见面会,与媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则。

设计模式中的LKP

中介者模式

在世界杯期间购买足球彩票,如果没有博彩公司作为中介,上千万的人一起计算赔率和输赢绝对是不可能的事情。博彩公司作为中介,每个人都只和博彩公司发生关联,博彩公司会根据所有人的投注情况计算好赔率,彩民们赢了钱就从博彩公司拿,输了钱就赔给博彩公司。

中介者模式很好地体现了最少知识原则。通过增加一个中介者对象,让所有的相关对象都通过中介者对象来通信,而不是互相引用。所以,当一个对象发生改变时,只需要通知中介者对象即可。

外观模式

外观模式在 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 个子系统打交道。如果其中的一个子系统发生了改变,那么客户的调用代码就得发生改变。而通过外观将客户和这些子系统隔开之后,如果修改子系统内部,只要外观不变,就不会影响客户的调用。同样,对外观的修改也不会影响到子系统,它们可以分别变化而互不影响。

封装在LKP中的体现

封装在很大程度上表达的是数据的隐藏。一个模块或者对象可以将内部的数据或者实现细节隐藏起来,只暴露必要的接口 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 

LKP原则的优缺点

  • 优点:LKP可以减少了对象之间的依赖。

  • 缺点:可能增加一些庞大到难以维护的第三者对象。

跟单一职责原则一样,在实际开发中,是否选择让代码符合最少知识原则,要根据具体的环境来定。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值