1. 前言
  2.  
  3. 本章我们要讲解的是S.O.L.I.D五大原则JavaScript语言实现的第3篇,里氏替换原则LSPThe Liskov Substitution Principle )。
  4.  
  5. 英文原文:http://freshbrewedcode.com/derekgreer/2011/12/31/solid-javascript-the-liskov-substitution-principle/
  6.  
  7. 开闭原则的描述是:
  8.  
  9. Subtypes must be substitutable for their base types.
  10. 派生类型必须可以替换它的基类型。
  11.  
  12. 在面向对象编程里,继承提供了一个机制让子类和共享基类的代码,这是通过在基类型里封装通用的数据和行为来实现的,然后已经及类型来声明更详细的子类型,为了应用里氏替换原则,继承子类型需要在语义上等价于基类型里的期望行为。
  13.  
  14. 为了来更好的理解,请参考如下代码:
  15.  
  16. function Vehicle(my) {
  17. var my = my || {};
  18. my.speed = ;
  19. my.running = false;
  20.  
  21. this.speed = function() {
  22. return my.speed;
  23. };
  24. this.start = function() {
  25. my.running = true;
  26. };
  27. this.stop = function() {
  28. my.running = false;
  29. };
  30. this.accelerate = function() {
  31. my.speed++;
  32. };
  33. this.decelerate = function() {
  34. my.speed--;
  35. }, this.state = function() {
  36. if (!my.running) {
  37. return "parked";
  38. }
  39. else if (my.running && my.speed) {
  40. return "moving";
  41. }
  42. else if (my.running) {
  43. return "idle";
  44. }
  45. };
  46. }
  47.  
  48. 上述代码我们定义了一个Vehicle函数,其构造函数为vehicle对象提供了一些基本的操作,我们来想想如果当前函数当前正运行在服务客户的产品环境上,如果现在需要添加一个新的构造函数来实现加快移动的vehicle。思考以后,我们写出了如下代码:
  49.  
  50. function FastVehicle(my) {
  51. var my = my || {};
  52.  
  53. var that = new Vehicle(my);
  54. that.accelerate = function() {
  55. my.speed += ;
  56. };
  57. return that;
  58. }
  59.  
  60. 在浏览器的控制台我们都测试了,所有的功能都是我们的预期,没有问题,FastVehicle的速度增快了3倍,而且继承他的方法也是按照我们的预期工作。此后,我们开始部署这个新版本的类库到产品环境上,可是我们却接到了新的构造函数导致现有的代码不能支持执行了,下面的代码段揭示了这个问题:
  61.  
  62. var maneuver = function(vehicle) {
  63. write(vehicle.state());
  64. vehicle.start();
  65. write(vehicle.state());
  66. vehicle.accelerate();
  67. write(vehicle.state());
  68. write(vehicle.speed());
  69. vehicle.decelerate();
  70. write(vehicle.speed());
  71. if (vehicle.state() != "idle") {
  72. throw "The vehicle is still moving!";
  73. }
  74. vehicle.stop();
  75. write(vehicle.state());
  76. };
  77.  
  78. 根据上面的代码,我们看到抛出的异常是“The vehicle is still moving!”,这是因为写这段代码的作者一直认为加速(accelerate)和减速(decelerate)的数字是一样的。但FastVehicle的代码和Vehicle的代码并不是完全能够替换掉的。因此,FastVehicle违反了里氏替换原则。
  79.  
  80. 在这点上,你可能会想:“但,客户端不能老假定vehicle都是按照这样的规则来做”,里氏替换原则(LSP)的妨碍(译者注:就是妨碍实现LSP的代码)不是基于我们所想的继承子类应该在行为里确保更新代码,而是这样的更新是否能在当前的期望中得到实现。
  81.  
  82. 上述代码这个case,解决这个不兼容的问题需要在vehicle类库或者客户端调用代码上进行一点重新设计,或者两者都要改。
  83.  
  84. 减少LSP妨碍
  85.  
  86. 那么,我们如何避免LSP妨碍?不幸的话,并不是一直都是可以做到的。我们这里有几个策略我们处理这个事情。
  87.  
  88. 契约(Contracts
  89.  
  90. 处理LSP过分妨碍的一个策略是使用契约,契约清单有2种形式:执行说明书(executable specifications)和错误处理,在执行说明书里,一个详细类库的契约也包括一组自动化测试,而错误处理是在代码里直接处理的,例如在前置条件,后置条件,常量检查等,可以从Bertrand Miller的大作《契约设计》中查看这个技术。虽然自动化测试和契约设计不在本篇文字的范围内,但当我们用的时候我还是推荐如下内容:
  91. 检查使用测试驱动开发(Test-Driven Development)来指导你代码的设计
  92. 设计可重用类库的时候可随意使用契约设计技术
  93.  
  94. 对于你自己要维护和实现的代码,使用契约设计趋向于添加很多不必要的代码,如果你要控制输入,添加测试是非常有必要的,如果你是类库作者,使用契约设计,你要注意不正确的使用方法以及让你的用户使之作为一个测试工具。
  95.  
  96. 避免继承
  97.  
  98. 避免LSP妨碍的另外一个测试是:如果可能的话,尽量不用继承,在Gamma的大作《Design Patterns Elements of Reusable Object-Orineted Software》中,我们可以看到如下建议:
  99.  
  100. Favor object composition over class inheritance
  101. 尽量使用对象组合而不是类继承
  102.  
  103. 有些书里讨论了组合比继承好的唯一作用是静态类型,基于类的语言(例如,在运行时可以改变行为),与JavaScript相关的一个问题是耦合,当使用继承的时候,继承子类型和他们的基类型耦合在一起了,就是说及类型的改变会影响到继承子类型。组合倾向于对象更小化,更容易想静态和动态语言语言维护。
  104.  
  105. 与行为有关,而不是继承
  106.  
  107. 到现在,我们讨论了和继承上下文在内的里氏替换原则,指示出JavaScript的面向对象实。不过,里氏替换原则(LSP)的本质不是真的和继承有关,而是行为兼容性。JavaScript是一个动态语言,一个对象的契约行为不是对象的类型决定的,而是对象期望的功能决定的。里氏替换原则的初始构想是作为继承的一个原则指南,等价于对象设计中的隐式接口。
  108.  
  109. 举例来说,让我们来看一下Robert C. Martin的大作《敏捷软件开发 原则、模式与实践》中的一个矩形类型:
  110.  
  111. 矩形例子
  112.  
  113. 考虑我们有一个程序用到下面这样的一个矩形对象:
  114.  
  115. var rectangle = {
  116. length: ,
  117. width:
  118. };
  119.  
  120. 过后,程序有需要一个正方形,由于正方形就是一个长(length)和宽(width)都一样的特殊矩形,所以我们觉得创建一个正方形代替矩形。我们添加了lengthwidth属性来匹配矩形的声明,但我们觉得使用属性的getters/setters一般我们可以让lengthwidth保存同步,确保声明的是一个正方形:
  121.  
  122. var square = {};
  123. (function() {
  124. var length = , width = ;
  125. // 注意defineProperty方式是262-5版的新特性
  126. Object.defineProperty(square, "length", {
  127. get: function() { return length; },
  128. set: function(value) { length = width = value; }
  129. });
  130. Object.defineProperty(square, "width", {
  131. get: function() { return width; },
  132. set: function(value) { length = width = value; }
  133. });
  134. })();
  135.  
  136. 不幸的是,当我们使用正方形代替矩形执行代码的时候发现了问题,其中一个计算矩形面积的方法如下:
  137.  
  138. var g = function(rectangle) {
  139. rectangle.length = ;
  140. rectangle.width = ;
  141. write(rectangle.length);
  142. write(rectangle.width);
  143. write(rectangle.length * rectangle.width);
  144. };
  145.  
  146. 该方法在调用的时候,结果是16,而不是期望的12,我们的正方形square对象违反了LSP原则,square的长度和宽度属性暗示着并不是和矩形100%兼容,但我们并不总是这样明确的暗示。解决这个问题,我们可以重新设计一个shape对象来实现程序,依据多边形的概念,我们声明rectanglesquarerelevant。不管怎么说,我们的目的是要说里氏替换原则并不只是继承,而是任何方法(其中的行为可以另外的行为)。
  147.  
  148. 总结
  149.  
  150. 里氏替换原则(LSP)表达的意思不是继承的关系,而是任何方法(只要该方法的行为能体会另外的行为就行)。
  151.  
  152. 同步与推荐
  153.  
  154. 本文已同步至目录索引:深入理解JavaScript系列
  155.  
  156. 深入理解JavaScript系列文章,包括了原创,翻译,转载等各类型的文章,如果对你有用,请推荐支持一把,给大叔写作的动力。

深入理解JavaScript系列(8):S.O.L.I.D五大原则之里氏替换原则LSP的更多相关文章

  1. 深入理解JavaScript系列

    转自http://www.cnblogs.com/TomXu/archive/2011/12/15/2288411.html 深入理解JavaScript系列(1):编写高质量JavaScript代码 ...

  2. 深入理解JavaScript系列(转自汤姆大叔)

    深入理解JavaScript系列文章,包括了原创,翻译,转载,整理等各类型文章,如果对你有用,请推荐支持一把,给大叔写作的动力. 深入理解JavaScript系列(1):编写高质量JavaScript ...

  3. [转]深入理解JavaScript系列

    文章转自:汤姆大叔-深入理解JavaScript系列文章 深入理解JavaScript系列文章,包括了原创,翻译,转载,整理等各类型文章,如果对你有用,请推荐支持一把,给大叔写作的动力. 深入理解Ja ...

  4. [转载]深入理解JavaScript系列 --汤姆大叔

    深入理解JavaScript系列文章,包括了原创,翻译,转载,整理等各类型文章,如果对你有用,请推荐支持一把,给大叔写作的动力. 深入理解JavaScript系列(1):编写高质量JavaScript ...

  5. 深入理解JavaScript系列(转载)

    深入理解JavaScript系列 深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点 深入理解JavaScript系列(2):揭秘命名函数表达式 深入理解JavaSc ...

  6. 深入理解JavaScript系列(22):S.O.L.I.D五大原则之依赖倒置原则DIP

    前言 本章我们要讲解的是S.O.L.I.D五大原则JavaScript语言实现的第5篇,依赖倒置原则LSP(The Dependency Inversion Principle ). 英文原文:htt ...

  7. 深入理解JavaScript系列(21):S.O.L.I.D五大原则之接口隔离原则ISP

    前言 本章我们要讲解的是S.O.L.I.D五大原则JavaScript语言实现的第4篇,接口隔离原则ISP(The Interface Segregation Principle). 英文原文:htt ...

  8. 深入理解JavaScript系列(7):S.O.L.I.D五大原则之开闭原则OCP

    前言 本章我们要讲解的是S.O.L.I.D五大原则JavaScript语言实现的第2篇,开闭原则OCP(The Open/Closed Principle ). 开闭原则的描述是: Software ...

  9. 深入理解JavaScript系列(6):S.O.L.I.D五大原则之单一职责SRP

    前言 Bob大叔提出并发扬了S.O.L.I.D五大原则,用来更好地进行面向对象编程,五大原则分别是: The Single Responsibility Principle(单一职责SRP) The ...

随机推荐

  1. java -jar jar包,运行报错没有主清单和无法加载主类

    jar: 包名(class 文件) META-INF(MANIFEST.MF ) .classpath 1.从eclipse直接导出的jar包: 2.修改MANIFEST.MF文件:

  2. python测试笔试题1

    哪一个方法用来返回变量类型? 答案 type 哪一个方法用来列出一个类下的所有属性,方法,以及变量? 答案 dir 字符串方法format是用来去掉字符串的左右空格的么? 答案 不是 python 的 ...

  3. MySQL参数log_bin_trust_function_creators

    问题:执行创建函数的sql文件报错如下: [Err] 1418 - This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA ...

  4. Windows系统如何安装Redis?

    转自 http://blog.csdn.net/lamp_yang_3533/article/details/52024744   一.Redis的下载地址 Redis官方并没有提供Redis的win ...

  5. 消息中间件ActiveMQ、RabbitMQ、RocketMQ、ZeroMQ、Kafka如何选型?

    最近要为公司的消息队列中间件进行选型,市面上相关的开源技术又非常多,如ActiveMQ.RabbitMQ.ZeroMQ.Kafka,还有阿里巴巴的RocketMQ等. 这么多技术,如何进行选型呢? 首 ...

  6. 在Delphi中获取和修改文件的时间

    转载自 http://www.cnblogs.com/jieke/archive/2013/01/11/2855782.html 本文介绍了在Delphi中利用系统函数和Windows API函数调用 ...

  7. 1.线性回归、Logistic回归、Softmax回归

    本次回归章节的思维导图版总结已经总结完毕,但自我感觉不甚理想.不知道是模型太简单还是由于自己本身的原因,总结出来的东西感觉很少,好像知识点都覆盖上了,但乍一看,好像又什么都没有.不管怎样,算是一次尝试 ...

  8. 190308python-MySQL

    一.Python连接MySQL import pymysql conn = pymysql.connect(host='192.168.100.4', port=3306, user='dongfei ...

  9. 【转】LAMBDAFICATOR: Crossing the gap from imperative to functional programming through refactorings

    Link:http://refactoring.info/tools/LambdaFicator/ Problem Description Java 8 will support lambda exp ...

  10. JAVASCRIPT 使用 && 和 || 完成 简写

    123=='1234' && 'active'    为真时,返回 'active '...可以当成 三元运算符的简写形式哦. let  val = val || 'active'  ...