最早由Barbara Liskov 由1988年提出:
子型態(subtype)必須要能夠替換它們的基底型態
最簡單的範例是:
就是在違反LSP的時候,常常也明顯違反OCP,就是在程式執行階段時,進行型別檢查,如以下
using System;
public class Program
{
public struct Point
{
public double x;
public double y;
}
public class Shape
{
public Shape(string shapeName)
{
ShapeName = shapeName;
}
public string ShapeName;
public static void DrawShape(Shape t)
{
if (t.ShapeName == "circle")
{
((Circle)t).Draw();
}
if (t.ShapeName == "square")
{
(t as Square).Draw();
}
}
}
public class Circle : Shape
{
public Point center;
public double radius;
public Circle(string typeName):base(typeName)
{
}
public void Draw()
{
Console.WriteLine("this is circle");
}
}
public class Square : Shape
{
public Point TopLeft;
public double side;
public Square(string typeName):base(typeName)
{
}
public void Draw()
{
Console.WriteLine("this is square");
}
}
public static void Main()
{
//註:這邊更好是用列舉的方式指定Name, 強制約定
Shape a = new Circle("circle");
Shape b = new Square("square");
Shape.DrawShape(a);
Shape.DrawShape(b);
}
}
很顯然,DrawShape違反了OCP,因為每擴充一種shape的時候,這個函式就要一併作出修正
書中提及很多人肯定這種作法是很糟糕設計,但為什麼會促使程式設計師寫出這種函式呢?
主要可能是對於多型的額外開銷(overhead)認為大得難以忍受,他沒有在shape類別中定義抽象方法,以便於抽象draw行為。
然而在現今電腦發展飛速的時代中,這種多型方法呼叫的開銷都是ns等級。其實不必要對此有過度的觀點
此例中,square類別和circle類別無法替換shape類別,就是違反了LSP, 同時又迫使DrawShape違反了OCP,因此LSP的違反也潛在地違反了OCP
關鍵的問題:
考慮以下程式
using System;
public class Program
{
public class Rectangle
{
protected double width;
protected double height;
public virtual double Height
{
get
{
return height;
}
set
{
height = value;
}
}
public virtual double Width
{
get
{
return width;
}
set
{
width = value;
}
}
public double Area()
{
return width*height;
}
}
public class Square :Rectangle
{
public override double Height
{
get
{
return base.height;
}
set
{
base.width = value;
base.height = value;
}
}
public override double Width
{
get
{
return base.width;
}
set
{
base.height = value;
base.width = value;
}
}
}
public static void Main()
{
Rectangle r = new Square();
r.Width = 5;
r.Height = 4;
//對正方向設定5再設定4的邊界,行為意圖比較像是拉動邊的長短而己
Console.WriteLine("square area is "+r.Area());
}
}
雖然square跟rectangle自己都可以正常運作,這種我們稱為子類別的自相容(self-consistent)及結果是正確的,可是這個結論是錯的
跑出來的結果:
square area is 16
但一個自相容的程式未必和他所有的使用端程式相容,考慮以下例子:
Rectangle r = new Square();
var result = CalculateArea(r);
public static double CalculateArea(Rectangle r)
{
r.Width = 4;
r.Height = 5;
if (r.Area() != 20)
throw new Exception("bad area");
return r.Area();
}
其結果反而是:
Run-time exception (line 89): bad area
因此關鍵的問題是,撰寫這個CalculateArea的程式,並不知道其傳進來的類別,改變長的時候會導致寬也被改變掉了
因此此函式作的假設,確因為傳入了square,導致發生了錯誤。因此square與rectangle之間的關係是違反LSP的
書中認為或許有人會針對這個函式所存在的問題進行爭論,認為此函式的作者不能假設長寬一定是獨立的。
但問題是此函式的作者不會同意這個論點,因為此命名以rectangle為命名作為參數,確實有一些不變性與真理足以說明。
而Rectangle其中一個不變的性質就是長寬可以獨立。此函式的作者可以斷言這個不變性(檢查)。
反而是square的作者違反了不變性。
而有 趣的是他不是違反square的不變性,而是違反了rectangle的不變性
此例告訴我們
一個模型,如果獨立來看,並不具備真正意義上的有效性,有效性只能以客戶端的程式來表現
解答LSP方針
1.OOD指出Is-A關係是從”行為”來判斷
2.基於契約的設計(DBC),在單元測試時指定契約,後面有具體例子時再討論
簡單摘要至此,足以深化LSP的觀點了,先這樣吧~
參考書摘-無瑕的程式碼-敏捷完整篇 物件導向原則、設計模式與C#實踐