你的位置:首页 > ASP.net教程

[ASP.net教程]敏捷软件开发:原则、模式与实践——第10章 LSP:Liskov替换原则


第10章 LSP:Liskov替换原则   

  Liskov替换原则:子类型(subtype)必须能够替换掉它们的基类型(base type)。


10.1 违反LSP的情形

10.1.1 简单例子

  对LSP的违反导致了OCP的违反:

struct Point { double x, y;}public enum ShapeType { square, circle };public class Shape{  private ShapeType type;  public Shape(ShapeType t) { type = t; }  public static void DrawShape(Shape s)  {    if (s.type == ShapeType.square)      (s as Square).Draw();    else if (s.type == ShapeType.circle)      (s as Circle).Draw();  }}public class Circle : Shape{  private Point center;  private double radius;  public Circle() : base(ShapeType.circle) { }  public void Draw() {/* draws the circle */}}public class Square : Shape{  private Point topLeft;  private double side;  public Square() : base(ShapeType.square) { }  public void Draw() {/* draws the square */}}

 

  很显然DrawShape函数违反了OCP。它必须知道Shape类每个可能的派生类,并且每次创建一个Shape类派生出的新类时都必须要更改它。


10.1.2 更微妙的违反情形

  下面是一个Rectangle类型:

public class Rectangle{  private Point topLeft;  private double width;  private double height;  public double Width  {    get { return width; }    set { width = value; }  }  public double Height  {    get { return height; }    set { height = value; }  }}

  某一天,用户要求添加正方形的功能。

  我们经常说继承是IS-A(是一个)关系。从一般意义上讲,一个正方形就是一个矩形。因此把Square类视为从Rectangle类派生是合乎逻辑的。不过,这种想法会带来一些微妙但几位值得重视的问题。一般来说,这些问题是很难遇见的,直到我们编写代码时才会发现。

  Square类并不同时需要height和width。但是Square仍会从Rectangle中继承它们。显然这是浪费。假设我们不十分关心内存效率。写出如下自相容的Rectangle类和Square类代码:

public class Rectangle{  private Point topLeft;  private double width;  private double height;  public virtual double Width  {    get { return width; }    set { width = value; }  }  public virtual double Height  {    get { return height; }    set { height = value; }  }}public class Square : Rectangle{  public override double Width  {    set    {      base.Width = value;      base.Height = value;    }  }  public override double Height  {    set    {      base.Height = value;      base.Width = value;    }  }}

 

真正的问题

  现在Square和Rectangle看起来都能够工作。这样看起来该设计似乎是自相容的、正确的。可是,这个结论是错误的。一个自相容的设计未必就和所有的用户程序相容。考虑如下函数:

  void g(Rectangle r)  {    r.Width = 5;    r.Height = 4;    if (r.Area() != 20)      throw new Exception("Bad area!");  }

 

  对于Rectangle来说,此函数运行正确,但是,如果传递进来的是Square对象就会抛出异常。所有,真正的问题是:函数g的编写者假设改变Rectangle的常不会导致宽的改变。

  显然,改变一个长方形的宽不会影响他的长是的假设是合理的!然而,并不是所有作为Rectangle传递的对象都满足这个假设。函数g对于Square、Rectangle层次结构来说是脆弱的。对于g来说,Square不能替换Rectangle,因此Square和Rectangle之间的关系是违反LSP的。

有效性并非本质属性

  一个模型,如果孤立的看,并不具有真正意义上的有效性。模型的有效性只能通过它的客户程序来表现。因此,像其他原则一样,只预测那些最明显的对于LSP的违反的情况而推迟所有其他的预测,直到出现相关的脆弱性的臭味时,才去处理它们。

ISA是关于行为的

  OOD中IS-A关系是就行为方式而言的,行为方式是可以进行合理假设的,是客户程序所依赖的。


10.2 用提取公共部分的方法代替继承

查看如下代码:

public class Line{  private Point p1;  private Point p2;  public Line(Point p1, Point p2) { this.p1 = p1; this.p2 = p2; }  public Point P1 { get { return p1; } }  public Point P2 { get { return p2; } }  public double Slope { get {/*code*/} }  public double YIntercept { get {/*code*/} }  public virtual bool IsOn(Point p) {/*code*/}}public class LineSegment : Line{  public LineSegment(Point p1, Point p2) : base(p1, p2) { }  public double Length() { get {/*code*/} }  public override bool IsOn(Point p) {/*code*/}}

 

  初看,会觉得它们之间自然有继承关系。但是,这两个类还是以微妙的方式违反了LSP。

  Line的使用者可以期望和该Line具有线性线性对应关系的所有点都在该Line上。例如,由YIntercept属性返回的点就是线和轴的交点。由于这个点和线具有线性对应关系,所以Line的使用者可以期望IsOn(YIntercept())==true。然而,对于许多LineSegment的实例,这条声明会失效。

  一个简单的方案可以解决Line和LineSegment的问题,该方案也阐明了一个OOD的重要工具。如果我们可以同时具有Line类和LineSegment类的访问权限,那么可以把这两个类的公共部分提出来一个抽象基类。如下:

public abstract class LinearObject{  private Point p1;  private Point p2;  public LinearObject(Point p1, Point p2)  { this.p1 = p1; this.p2 = p2; }  public Point P1 { get { return p1; } }  public Point P2 { get { return p2; } }  public double Slope { get {/*code*/} }  public double YIntercept { get {/*code*/} }  public virtual bool IsOn(Point p) {/*code*/}}public class Line : LinearObject{  public Line(Point p1, Point p2) : base(p1, p2) { }  public override bool IsOn(Point p) {/*code*/}}public class LineSegment : LinearObject{  public LineSegment(Point p1, Point p2) : base(p1, p2) { }  public double GetLength() {/*code*/}  public override bool IsOn(Point p) {/*code*/}}

 

  提取公共部分是一个有效的工具。如果两个类中有一些公共的特性,那么很可能稍后出现的其他类也会要这些特性。例如Ray类:

public class Ray : LinearObject{  public Ray(Point p1, Point p2) : base(p1, p2) {/*code*/}  public override bool IsOn(Point p) {/*code*/}}

 


10.3 启发式规则和习惯用法

  完成的功能少于基类的派生类通常是不能替换其类的,因此就违反了LSP。

  查看如下代码:

public class Base{  public virtual void f() {/*some code*/}}public class Derived : Base{  public override void f() { }}

 

  在Base中实现了函数f。不过,在Derived中,函数f是退化的。也许,Derived的编程者认为函数f在Derived中没有用处。遗憾的是,Base的使用者不知道他们不应该调用f,因此就出现了一个替换违规。

  在退化类中存在退化函数并不总是表示违反了LSP,但是当存在这种情况时,还是值得注意一下的。


10.4 结论  
  OCP是OOD中很多说法的核心。LSP是使OCP成为可能的主要原因之一。
  术语IS-A的含义过于宽泛以至于不能作为子类型的定义。子类型的正确定义是可替换的。

 

 

摘自:《敏捷软件开发:原则、模式与实践(C#版)》Robert C.Martin    Micah Martin 著

转载请注明出处:

作者:JesseLZJ
出处:http://jesselzj.cnblogs.com




韩国旅游网韩国特价旅游韩国旅游时间韩国旅游跟团现在去韩国旅游多少钱长沙火车站到株洲方特大巴几点发车?长沙火车站到株洲方特汽车在哪坐? 淄博到跑马岭野生动物园开车怎么走?淄博到济南跑马岭自驾车路线? 五一东方水世界有什么好玩的?杭州东方水世界五一活动介绍? 泰安到跑马岭野生动物园自驾车路线?泰安到济南跑马岭开车怎么走? 澳门最便宜住宿是在哪里?一般是多少钱呢? 五月份去香港女人街买东西要注意些什么? 香港维多利亚港会举办什么活动? 澳门龙环葡韵住宅式博物馆好玩吗? 现在迪拜天气怎么样啊?适合什么时候去游玩? 曼谷火车站在哪里?怎么买票?曼谷到清迈的车票多少钱? 去马尔代夫玩需要带什么呢? 听说泰国神殿寺有座佛像很灵,请问是哪座? 2015珠海春季赏花的好去处?珠海去哪里赏牡丹花? 2015圆明新园牡丹花开放了吗?珠海圆明新园山茶花盛花期? 春雪舞冰城 哈尔滨最浪漫的时刻 舌尖上的吴江 同里古镇品味江南 1808CC393ZATME Datasheet 1808CC393ZATME Datasheet 0402YJ100GBWTR\500 Datasheet 0402YJ100GBWTR\500 Datasheet 1808CC471MAT1A Datasheet 1808CC471MAT1A Datasheet 卢森堡是哪个国家 卢森堡是哪个国家 卢森堡是哪个国家 桂林龙脊梯田 桂林龙脊梯田 桂林龙脊梯田 迪士尼乐园图片 迪士尼乐园图片 迪士尼乐园图片