1. 程式人生 > >學習筆記:Liskov替換原則和繼承的使用

學習筆記:Liskov替換原則和繼承的使用

#include <vector>

class Bird{ 
public:
    virtual void Fly(){} 
};

class Sparrow : public Bird{
public:
    virtual void Fly(){}
};

class Eagle : public Bird{
public:
    virtual void Fly(){}
};

class Penguin : public Bird{
public:
    virtual void Fly(){ throw std::exception("Penguin can not fly");}
};

void FlyAll(std::vector<Bird*>& v){
    for( Bird* b : v){ 
        b->Fly();
    }
}

void main()
{
    std::vector<Bird*> v ;
    v.push_back(new Eagle);
    v.push_back(new Sparrow);
    v.push_back(new Penguin);

    FlyAll(v);
    
    system("pause");
    return ;
}

為了重用FlyAll(std::vector<Bird*>&)函式,對Bird*類提出約束:要求提供Fly虛擬函式,還有Fly必須成功。企鵝類違反了鳥類對外的公開承諾:會飛。當把企鵝物件強行用於FlyAll函式時就會發生嚴重問題。penguin類違反了Liskov原則。這個繼承體系是有問題的。

Pengui模組能替換Bird模組良好工作於FlyAll,還依賴於下列條件:1)Pengui模組的前置條件要弱於Bird模組。例如:如果Pengui要求要先吃飽魚,睡空調屋才能答應起飛,則這個前置條件太苛刻,別人很難驅動它去工作。2)Pengui模組的後置條件要強於Bird模組。例如:鷹類不但會飛,還飛出花樣,飛出氣勢,這個肯定是沒問題的。相反,企鵝類根本飛不出後置條件。

違反Liskov原則的繼承體系了,必然要對原有的舊程式碼做調整,進一步違反了對“新增開放,對修改封閉”的《開放封閉》原則。

void Fly(std::vector<Bird*>& v){
    for( Bird* b : v){ 
        if (dynamic_cast<Penguin*>(b)) continue;
        b->Fly();
    }
}

繼承的目的:不是重用基類程式碼,而是被重用於適用於基類的舊程式碼裡(就像FlyAll那樣的舊程式碼)

如果想重用基類的程式碼,有其他方法:委託,或者聚合,組合等。為什麼非要走繼承這條路才能重用?也許是這是從日常生活經驗獲得的思維定勢:繼承家產,就可以使用家產;繼承文化傳統,就可以使用這些知識了。