子類型必須能夠替換掉他們的父類型
這里的所有觀點摘抄自《敏捷軟件開發(fā)原則戈抄、模式與實踐》歌懒,原著Robert C. Martin具壮,鄧輝等譯茶袒。
李氏替換原則
假設(shè)有一個函數(shù)f梯刚,接受一個指向某個基類B的指針或者引用。如果把B的派生類D的對象作為參數(shù)傳給f薪寓,會導(dǎo)致f出現(xiàn)錯誤行為亡资。那么D就違反了LSP。
另外一種情況向叉,假定要對傳入的對象D做測試锥腻,看D是否滿足f所需要的條件。這個測試就會違反OCP母谎。原因是什么呢旷太?因為f需要的行為是B所具有的行為。
但是對于B的派生類卻沒有這樣的要求销睁。要讓測試D的用例通過供璧,就必須修改f,這樣就違反了對修改封閉的原則冻记,即OCP睡毒。
看一個違反LSP的例子:
struct Point (double x, y);
struct Shape {
enum ShapeType { square, circle } itsType;
Shape(ShapeType t) : itsType(t) {}
}
struct Circle: public Shape
{
Circle(): Shape(circle) {};
void Draw() const;
Point itsCenter;
double itsRadius;
}
struct Square: public Shape
{
Square(): Shape(square) {};
void Draw() const;
Point itsTopLeft;
double itsSide;
}
void DrawShape(const Shape& s)
{
if (s.itsType == Shape::square)
static_cast<const Square&>(s).Draw();
else if (s.itsType == Shape::circle)
static_cast<const Circle&>(s).Draw();
}
很明顯的,上述代碼違反了OCP原則冗栗,因為我們新增一種形狀演顾,都會導(dǎo)致源代碼的修改,需要添加新的if else隅居。
這個例子也理所當(dāng)然的違反了LSP钠至,因為派生類Circle和Square并不能替換掉Shape. 除非Shape中包含Draw并且將Draw設(shè)置為虛函數(shù)。
另外一個有名的例子就是矩形和正方形的問題胎源。我們在數(shù)學(xué)中會把正方形當(dāng)做矩形的一個特例棉钧。從而影響到我們在開發(fā)過程中,也會理所當(dāng)然的
認(rèn)為正方形應(yīng)該是從矩形派生而來涕蚤。
但實際上應(yīng)該考慮清楚宪卿,正方形和矩形,并非是一個繼承的關(guān)系万栅。矩形的長寬可以獨(dú)立調(diào)整佑钾,如果正方形派生自矩形,那么正方形的長寬也應(yīng)該可以
獨(dú)立的調(diào)整烦粒。但實際上正方形長寬是固定相等的休溶。
有人可能會說,可以在正方形的類中特殊處理。例如:
void Square::SetWidth(double w)
{
Rectangle::SetWidth(w);
Rectangle::SetHeight(w);
}
void Square::SetHeight(double w)
{
Rectangle::SetWidth(w);
Rectangle::SetHeight(w);
}
class Rectangle
{
public:
virtual void SetWidth(double w);
virtual void SetHeight(double w);
}
void f(Rectangle& r)
{
r.SetWidth(23);
}
上面的代碼看起來運(yùn)行沒有問題兽掰,Square類的對象也可以作為參數(shù)傳入f芭碍,但實際上隱藏了本質(zhì)上的問題。也就是認(rèn)知上的問題禾进。
考慮如下的代碼:
void ch(Rectange& r)
{
r.SetWidth(22);
r.SetHeight(30);
}
如果傳入了一個正方形的對象豁跑,我們最終期望的正方形邊長應(yīng)該是多大廉涕?使用者只會知道泻云,可以傳入正方形,并期望傳入之后
滿足矩形的行為狐蜕。但實際上會出現(xiàn)令人迷惑的結(jié)果宠纯。
這個有點類似于掛羊頭賣狗肉的感覺,實際上需要的羊肉的味道层释,但是你給的確實狗肉婆瓜,出來的結(jié)果可想而知。
如何識別
經(jīng)過上面的幾個例子贡羔,可以看到廉白,如果單單從一個問題來看,也許系統(tǒng)是滿足條件的乖寒。違反不違反LSP并沒有多大的壞處猴蹂。但是,
從設(shè)計的使用者的角度楣嘁,就可以看出磅轻,違反了LSP原則會給系統(tǒng)的穩(wěn)定性帶來多大的影響。
但是有誰能夠知道設(shè)計的使用者會做出怎樣的合理假設(shè)呢逐虚?大多數(shù)這樣的假設(shè)都很難預(yù)測聋溜。事實上如果試圖去猜測使用者的所有
假設(shè),會讓系統(tǒng)無比復(fù)雜叭爱。最好的方法就是只預(yù)測那些明顯對于LSP違反的情況撮躁。
繼續(xù)深入的思考一下,LSP所描述的是一個嚴(yán)格的IS-A的關(guān)系买雾。我們從認(rèn)知上來看馒胆,正方形似乎是一個矩形,是它的一個特例凝果。
但是從使用者的角度祝迂,正方形卻處處和長方形不兼容。那么真正可以區(qū)分是否滿足LSP(也就是滿足IS-A)的條件是什么呢器净?
- 對象的行為方式
因為正方形和長方形所預(yù)期的行為方式不相容型雳,所以不滿足LSP。
所以,考察一個繼承關(guān)系是否滿足LSP纠俭,最終看的是派生的類和基類是否具有相同的行為方式沿量。
具體的考察行為方式的辦法有兩種:
- 基于契約的設(shè)計(DBC)
- 單元測試中指定契約