A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 LiuKang 于 2013-11-19 21:17 编辑

定义:如果对于类型S的每个对象O1存在类型T的对象O2,那么对于所有定义了T的程序P来说,当用O1替换O2并且S是T的子类型时,P的行为不会改变。子类型能够完全替换父类型,而不会让调用父类型的客户程序从行为上有任何改变。
例:
白马、黑马:
反过来的代换不成立《墨子·小取》说:"娣,美人也,爱娣,非爱美人也……"娣便是妹妹,哥哥喜爱妹妹,是因为两人是兄妹关系,而不是因为妹妹是个美人。因此,喜爱妹妹不等同于喜爱美人。用面向对象语言描述,美人是基类,妹妹是美人的子类。哥哥作为一个有"喜爱()"方法,接受妹妹作为参数。那么,这个"喜爱()"方法一般不能接受美人的实例。

一个违反LSP的简单例子(长方形和正方形)
public class Rectangle{   private long width;   private long height;       public void setWidth(long width)   {      this.width = width;   }   public long getWidth()   {      return this.width;   }   public void setHeight(long height)   {      this.height = height;   }   public long getHeight()   {      return this.height;   }}
public class Square{   private long side;       public void setSide(long side)   {      this.side = side;   }
   public long getSide()   {      return side;   }}
正方形不可以做长方形的子类
using System;
public class Rectangle{   private long width;   private long height;       public void setWidth(long width)   {      this.width = width;   }   public long getWidth()   {      return this.width;   }   public void setHeight(long height)   {      this.height = height;   }   public long getHeight()   {      return this.height;   }}
public class Square : Rectangle{   private long side;
   public void setWidth(long width)   {      setSide(width);   }
   public long getWidth()   {      return getSide();   }
   public void setHeight(long height)   {      setSide(height);   }
   public long getHeight()   {      return getSide();   }
   public long getSide()   {      return side;   }
   public void setSide(long side)   {      this.side = side;   }}
public class SmartTest{   public void resize(Rectangle r)   {      while (r.getHeight() >= r.getWidth() )      {         r.setWidth(r.getWidth() + 1);      }   }}  在执行SmartTest的resize方法时,如果传入的是长方形对象,当高度大于宽度时,会自动增加宽度直到超出高度。但是如果传入的是正方形对象,则会陷入死循环。
代码重构
public interface Quadrangle{   public long getWidth();   public long getHeight();}
public class Rectangle : Quadrangle {   private long width;   private long height;       public void setWidth(long width)   {      this.width = width;   }   public long getWidth()   {      return this.width;   }   public void setHeight(long height)   {      this.height = height;   }   public long getHeight()   {      return this.height;   }}
public class Square : Quadrangle {   private long side;
   public void setSide(long side)   {      this.side = side;   }
   public long getSide()   {      return side;   }
   public long getWidth()   {      return getSide();   }
   public long getHeight()   {      return getSide();   }}

  Liskov替换原则 基于这样的一个事实,那就是客户程序在调用某一个类时,实际上是对该类的整个继承体系设定了一个契约,继承体系中的所有类必须遵循这一契约,即前置条件和后置条件必须保持一致 。这就超越了继承中子类与父类之间形成的is-a关系,为对象继承加上了一把严格的枷锁。显然,Liskov替换原则对于约束继承的泛滥具有重要意义。
  
  我个人认为,Liskov替换原则并不是要求子类不能新增父类没有的方法或者属性。因为从调用父类的客户程序的角度来说,它关心的仅仅是父类的行为,只要子类对于父类的行为是可替换的,就不算是违背该原则。恰恰相反,当你发现父类拥有子类不希望继承,或者勉强继承会对子类造成破坏时 ,正可以说明这个继承体系可能存在问题,违背了Liskov替换原则。这就充分说明,子类并不关心父类的行为,但却需要遵循父类制定的规范或契约,以满足客户调用父类的期望。正所谓"萧规曹随",如果前人制定的规范我们不遵循,反而要去打破,那就不是继承,而是铁了心要另起炉灶了。
  
   一个经典的违反Liskov替换原则的例子是正方形与矩形之间的关系。这样的例子在谈对象设计的原则时,已经啰嗦得够多,这里我就不再赘述了 。这个例子带来的教训就是,现实世界中继承的例子,不能够完全直接套用在程序世界中。不过,作为设计的参照物,现实世界的很多规律与法则,我们仍然不可忽视。例如鲸鱼和鱼,应该属于什么关系?从生物学的角度看,鲸鱼应该属于哺乳动物,而不是鱼类。没错,在程序世界中我们可以得出同样的结论。如果让鲸鱼类去继承鱼类,就完全违背了Liskov替换原则。因为鱼作为父类,很多特性是鲸鱼所不具备的,例如通过腮呼吸,以及卵生繁殖。那么,二者是否具有共性呢?有,那就是它们都可以在水中"游泳",从程序设计的角度来说,它们都共同实现了一个支持"游泳"行为的接口。
  
  如图2-7所示的设计,可以看做是解决违背Liskov替换原则的一种常规方案,即提取两者之间的共同点,定义一个更为通用的接口,或者新的父类。

图2-7  鲸鱼和鱼的关系
更多图片 小图 大图
组图打开中,请稍候......

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马