单一职责原则
单一职责原则(SRP)的职责被定义为“引起变化的原因”。如果我们有两个动机去改写一个方法,那么这个方法就具有两个职责。如果一个方法承担了过多的职责,那么在需求的变迁过程中,需要改写这个方法的可能性就越大。
因此,SRP 原则体现为:一个对象(方法)只做一件事情。
SRP原则在很多设计模式中都有着广泛的运用,例如代理模式、迭代器模式、单例模式和装饰器模式。具体例子可以看之前的几篇:【青训营】- JavaScript中常见设计模式(上篇)、【青训营】- JavaScript中常见设计模式(中篇)、【青训营】- JavaScript中常见设计模式(下篇)。
在代理模式中,我们就通过增加虚拟代理的方式,把预加载图片的职责放到代理对象中,而本体仅仅负责往页面中添加img标签。
var myImage = (function () {
var imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return {
setSrc: function (src) {
imgNode.src = src;
}
}
})();
var proxyImage = (function () {
var img = new Image;
img.onload = function () {
myImage.setSrc(this.src);
}
return {
setSrc: function (src) {
myImage.setSrc('./loading.gif');
img.src = src;
}
}
})();
proxyImage.setSrc('http://xxxxx.jpg');
那么应该如何分离职责呢?要明确的是,并不是所有的职责都应该一一分离。如果随着需求的变化,有两个职责总是同时变化,那就不必分离他们,并且,当且仅当它们确定会发生变化时才具有意义。
此外,我们未必要在任何时候都一成不变地遵守原则,因为种种原因违反SRP的情况并不少见。比如jQuery 的attr等方法,既负责赋值,又负责取值。在方便性与稳定性之间要有一些取舍。
SRP原则的优点是降低了单个类或者对象的复杂度,这有助于代码的复用,也有利于进行单元测试。但SRP原则也有一些缺点,最明显的是会增加编写代码的复杂度。当我们按照职责把对象分解成更小的粒度之后,实际上也增大了这些对象之间相互联系的难度。
最少知识原则
最少知识原则(LKP)说的是一个软件实体应当尽可能少地与其他实体发生相互作用。这里的软件实体是一个广义的概念,不仅包括对象,还包括系统、类、模块、函数、变量等。
单一职责原则指导我们把对象划分成较小的粒度,这可以提高对象的可复用性。但越来越多的对象之间可能会产生错综复杂的联系。而最少知识原则要求我们在设计程序时,应当尽量减少对象之间的交互。如果两个对象之间不必彼此直接通信,那么这两个对象就不要发生直接的相互联系。常见的做法是引入一个第三者对象,来承担这些对象之间的通信作用。
最少知识原则在设计模式中体现得最多的地方是中介者模式和外观模式。
虽然遵守最小知识原则减少了对象之间的依赖,但也有可能增加一些庞大到难以维护的第三者对象。跟单一职责原则一样,在实际开发中,是否选择让代码符合最少知识原则,要根据具体的环境来定。
开放-封闭原则
开放-封闭原则最早由Eiffel语言的设计者Bertrand Meyer在其著作Object-Oriented Software Construction中提出。它的定义如下:
软件实体(类、模块、函数)等应该是可以扩展的,但是不可修改。
开放-封闭原则的思想:当需要改变一个程序的功能或者给这个程序增加新功能的时候,可以使用增加代码的方式,但是不允许改动程序的源代码。
在诸如发布-订阅模式、模板方法模式、策略模式、代理模式、职责链模式中都有涉及。
在职责链模式代码中,大家也许会产生这个疑问:开放-封闭原则要求我们只能通过增加源代码的方式扩展程序的功能,而不允许修改源代码。那当我们往职责链中增加一个新的100元订单函数节点时,不也必须改动设置链条的代码吗?
order500yuan.setNextSuccessor( order200yuan ).setNextSuccessor( orderNormal );
变为
order500yuan.setNextSuccessor( order200yuan ).setNextSuccessor( order100yuan ).setNextSuccessor( orderNormal );
实际上,让程序保持完全封闭是不容易做到的。而且让程序符合开放-封闭原则的代价是引入更多的抽象层次,更多的抽象有可能会增大代码的复杂度。更何况,有一些代码是无论如何也不能完全封闭的。
因此,对于开放-封闭原则,我们需要做到两点。
- 挑选出最容易发生变化的地方,然后构造抽象来封闭这些变化。
- 在不可避免发生修改的时候,尽量修改那些相对容易修改的地方。
参考资料
《JavaScript设计模式与开发实践》
《Object-Oriented Software Construction》