深入理解JavaScript系列(8) S.O.L.I.D五大原则之里氏替换原则LSP


Posted in Javascript onJanuary 15, 2012

前言
本章我们要讲解的是S.O.L.I.D五大原则JavaScript语言实现的第3篇,里氏替换原则LSP(The Liskov Substitution Principle )。

英文原文:http://freshbrewedcode.com/derekgreer/2011/12/31/solid-javascript-the-liskov-substitution-principle/
复制代码
开闭原则的描述是:

Subtypes must be substitutable for their base types.
派生类型必须可以替换它的基类型。
复制代码
在面向对象编程里,继承提供了一个机制让子类和共享基类的代码,这是通过在基类型里封装通用的数据和行为来实现的,然后已经及类型来声明更详细的子类型,为了应用里氏替换原则,继承子类型需要在语义上等价于基类型里的期望行为。

为了来更好的理解,请参考如下代码:

function Vehicle(my) { 
var my = my || {}; 
my.speed = 0; 
my.running = false; this.speed = function() { 
return my.speed; 
}; 
this.start = function() { 
my.running = true; 
}; 
this.stop = function() { 
my.running = false; 
}; 
this.accelerate = function() { 
my.speed++; 
}; 
this.decelerate = function() { 
my.speed--; 
}, this.state = function() { 
if (!my.running) { 
return "parked"; 
} 
else if (my.running && my.speed) { 
return "moving"; 
} 
else if (my.running) { 
return "idle"; 
} 
}; 
}

上述代码我们定义了一个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违反了里氏替换原则。

在这点上,你可能会想:“但,客户端不能老假定vehicle都是按照这样的规则来做”,里氏替换原则(LSP)的妨碍(译者注:就是妨碍实现LSP的代码)不是基于我们所想的继承子类应该在行为里确保更新代码,而是这样的更新是否能在当前的期望中得到实现。

上述代码这个case,解决这个不兼容的问题需要在vehicle类库或者客户端调用代码上进行一点重新设计,或者两者都要改。

减少LSP妨碍
那么,我们如何避免LSP妨碍?不幸的话,并不是一直都是可以做到的。我们这里有几个策略我们处理这个事情。

契约(Contracts)
处理LSP过分妨碍的一个策略是使用契约,契约清单有2种形式:执行说明书(executable specifications)和错误处理,在执行说明书里,一个详细类库的契约也包括一组自动化测试,而错误处理是在代码里直接处理的,例如在前置条件,后置条件,常量检查等,可以从Bertrand Miller的大作《契约设计》中查看这个技术。虽然自动化测试和契约设计不在本篇文字的范围内,但当我们用的时候我还是推荐如下内容:

检查使用测试驱动开发(Test-Driven Development)来指导你代码的设计
设计可重用类库的时候可随意使用契约设计技术
对于你自己要维护和实现的代码,使用契约设计趋向于添加很多不必要的代码,如果你要控制输入,添加测试是非常有必要的,如果你是类库作者,使用契约设计,你要注意不正确的使用方法以及让你的用户使之作为一个测试工具。

避免继承
避免LSP妨碍的另外一个测试是:如果可能的话,尽量不用继承,在Gamma的大作《Design Patterns ? Elements of Reusable Object-Orineted Software》中,我们可以看到如下建议:

Favor object composition over class inheritance
尽量使用对象组合而不是类继承
复制代码
有些书里讨论了组合比继承好的唯一作用是静态类型,基于类的语言(例如,在运行时可以改变行为),与JavaScript相关的一个问题是耦合,当使用继承的时候,继承子类型和他们的基类型耦合在一起了,就是说及类型的改变会影响到继承子类型。组合倾向于对象更小化,更容易想静态和动态语言语言维护。

与行为有关,而不是继承
到现在,我们讨论了和继承上下文在内的里氏替换原则,指示出JavaScript的面向对象实。不过,里氏替换原则(LSP)的本质不是真的和继承有关,而是行为兼容性。JavaScript是一个动态语言,一个对象的契约行为不是对象的类型决定的,而是对象期望的功能决定的。里氏替换原则的初始构想是作为继承的一个原则指南,等价于对象设计中的隐式接口。

举例来说,让我们来看一下Robert C. Martin的大作《敏捷软件开发 原则、模式与实践》中的一个矩形类型:

矩形例子
考虑我们有一个程序用到下面这样的一个矩形对象:

var rectangle = { 
length: 0, 
width: 0 
}; 
[code] 
过后,程序有需要一个正方形,由于正方形就是一个长(length)和宽(width)都一样的特殊矩形,所以我们觉得创建一个正方形代替矩形。我们添加了length和width属性来匹配矩形的声明,但我们觉得使用属性的getters/setters一般我们可以让length和width保存同步,确保声明的是一个正方形: 
[code] 
var square = {}; 
(function() { 
var length = 0, width = 0; 
// 注意defineProperty方式是262-5版的新特性 
Object.defineProperty(square, "length", { 
get: function() { return length; }, 
set: function(value) { length = width = value; } 
}); 
Object.defineProperty(square, "width", { 
get: function() { return width; }, 
set: function(value) { length = width = value; } 
}); 
})();

不幸的是,当我们使用正方形代替矩形执行代码的时候发现了问题,其中一个计算矩形面积的方法如下:
var g = function(rectangle) { 
rectangle.length = 3; 
rectangle.width = 4; 
write(rectangle.length); 
write(rectangle.width); 
write(rectangle.length * rectangle.width); 
};

该方法在调用的时候,结果是16,而不是期望的12,我们的正方形square对象违反了LSP原则,square的长度和宽度属性暗示着并不是和矩形100%兼容,但我们并不总是这样明确的暗示。解决这个问题,我们可以重新设计一个shape对象来实现程序,依据多边形的概念,我们声明rectangle和square,relevant。不管怎么说,我们的目的是要说里氏替换原则并不只是继承,而是任何方法(其中的行为可以另外的行为)。

总结
里氏替换原则(LSP)表达的意思不是继承的关系,而是任何方法(只要该方法的行为能体会另外的行为就行)。

Javascript 相关文章推荐
Add Formatted Text to a Word Document
Jun 15 Javascript
神奇的7个jQuery 3D插件整理
Jan 06 Javascript
分享10篇优秀的jQuery幻灯片制作教程及应用案例
Apr 16 Javascript
Jquery easyui 下loaing效果示例代码
Aug 12 Javascript
JQuery中form验证出错信息的查看方法
Oct 08 Javascript
javascript移出节点removeChild()使用介绍
Apr 03 Javascript
javascript格式化json显示实例分析
Apr 21 Javascript
BootStrap初学者对弹出框和进度条的使用感觉
Jun 27 Javascript
基于Vue实现平滑过渡的拖拽排序功能
Jun 12 Javascript
vue2.x 通过后端接口代理,获取qq音乐api的数据示例
Oct 30 Javascript
JavaScript接口实现方法实例分析
May 16 Javascript
vue实现按钮切换图片
Jan 20 Vue.js
深入理解JavaScript系列(7) S.O.L.I.D五大原则之开闭原则OCP
Jan 15 #Javascript
深入理解JavaScript系列(6):S.O.L.I.D五大原则之单一职责SRP
Jan 15 #Javascript
深入理解JavaScript系列(6) 强大的原型和原型链
Jan 15 #Javascript
深入理解JavaScript系列(4) 立即调用的函数表达式
Jan 15 #Javascript
深入理解JavaScript系列(3) 全面解析Module模式
Jan 15 #Javascript
深入理解JavaScript系列(2) 揭秘命名函数表达式
Jan 15 #Javascript
深入理解JavaScript系列(1) 编写高质量JavaScript代码的基本要点
Jan 15 #Javascript
You might like
全国FM电台频率大全 - 14 江西省
2020/03/11 无线电
利用php来自动调用不同服务器上的flash
2006/10/09 PHP
WML,Apache,和 PHP 的介绍
2006/10/09 PHP
PHP运行模式的深入理解
2013/06/03 PHP
PHP去掉json字符串中的反斜杠\及去掉双引号前的反斜杠
2015/09/30 PHP
CodeIgniter基于Email类发邮件的方法
2016/03/29 PHP
iis6+javascript Add an Extension File
2007/06/13 Javascript
JavaScript中去掉数组中的重复值的实现方法
2011/08/03 Javascript
jquery选择器排除某个DOM元素的方法(实例演示)
2014/04/25 Javascript
一个JavaScript处理textarea中的字符成每一行实例
2014/09/22 Javascript
js代码实现点击按钮出现60秒倒计时
2021/01/28 Javascript
JavaScript & jQuery完美判断图片是否加载完毕
2017/01/08 Javascript
对比分析Django的Q查询及AngularJS的Datatables分页插件
2017/02/07 Javascript
微信小程序 蓝牙的实现实例代码
2017/06/27 Javascript
微信小程序中使用自定义图标(阿里icon)的方法
2018/08/20 Javascript
解决layui checkbox 提交多个值的问题
2019/09/02 Javascript
[06:20]2015国际邀请赛第三日top10
2015/08/08 DOTA
把项目从Python2.x移植到Python3.x的经验总结
2015/04/20 Python
Python排序算法实例代码
2017/08/10 Python
Python内存管理方式和垃圾回收算法解析
2017/11/11 Python
Python功能点实现:函数级/代码块级计时器
2019/01/02 Python
妙用itchat! python实现久坐提醒功能
2019/11/25 Python
python相对企业语言优势在哪
2020/06/12 Python
解析HTML5的存储功能和web SQL的相关操作方法
2016/02/19 HTML / CSS
美国在线鞋类零售商:LifeStride
2019/06/09 全球购物
工商学院毕业生个人自我评价
2013/09/19 职场文书
后勤人员岗位职责
2013/12/17 职场文书
外企求职信范文分享
2013/12/31 职场文书
物理教育专业求职信
2014/06/25 职场文书
岗位职责说明书模板
2014/07/30 职场文书
信仰心得体会
2014/09/05 职场文书
关于十八大的演讲稿
2014/09/15 职场文书
2016特色励志班级口号
2015/12/24 职场文书
年会邀请函的格式及范文五篇
2019/11/02 职场文书
nginx基于域名,端口,不同IP的虚拟主机设置的实现
2021/03/31 Servers
python解析json数据
2022/04/29 Python