Subtypes must be substitutable for their base types. 派生类型必须可以替换它的基类型。 复制代码 在面向对象编程里,继承提供了一个机制让子类和共享基类的代码,这是通过在基类型里封装通用的数据和行为来实现的,然后已经及类型来声明更详细的子类型,为了应用里氏替换原则,继承子类型需要在语义上等价于基类型里的期望行为。
为了来更好的理解,请参考如下代码: 复制代码 代码如下: function Vehicle(my) { var my = my || {}; my.speed = 0; my.running = false;
上述代码我们定义了一个Vehicle函数,其构造函数为vehicle对象提供了一些基本的操作,我们来想想如果当前函数当前正运行在服务客户的产品环境上,如果现在需要添加一个新的构造函数来实现加快移动的vehicle。思考以后,我们写出了如下代码: 复制代码 代码如下: function FastVehicle(my) { var my = my || {};
var that = new Vehicle(my); that.accelerate = function() { my.speed += 3; }; return that; }
在浏览器的控制台我们都测试了,所有的功能都是我们的预期,没有问题,FastVehicle的速度增快了3倍,而且继承他的方法也是按照我们的预期工作。此后,我们开始部署这个新版本的类库到产品环境上,可是我们却接到了新的构造函数导致现有的代码不能支持执行了,下面的代码段揭示了这个问题: 复制代码 代码如下: var maneuver = function(vehicle) { write(vehicle.state()); vehicle.start(); write(vehicle.state()); vehicle.accelerate(); write(vehicle.state()); write(vehicle.speed()); vehicle.decelerate(); write(vehicle.speed()); if (vehicle.state() != "idle") { throw "The vehicle is still moving!"; } vehicle.stop(); write(vehicle.state()); };
根据上面的代码,我们看到抛出的异常是“The vehicle is still moving!”,这是因为写这段代码的作者一直认为加速(accelerate)和减速(decelerate)的数字是一样的。但FastVehicle的代码和Vehicle的代码并不是完全能够替换掉的。因此,FastVehicle违反了里氏替换原则。
避免继承 避免LSP妨碍的另外一个测试是:如果可能的话,尽量不用继承,在Gamma的大作《Design Patterns – Elements of Reusable Object-Orineted Software》中,我们可以看到如下建议:
Favor object composition over class inheritance 尽量使用对象组合而不是类继承 复制代码 有些书里讨论了组合比继承好的唯一作用是静态类型,基于类的语言(例如,在运行时可以改变行为),与JavaScript相关的一个问题是耦合,当使用继承的时候,继承子类型和他们的基类型耦合在一起了,就是说及类型的改变会影响到继承子类型。组合倾向于对象更小化,更容易想静态和动态语言语言维护。