學習筆記:Liskov替換原則和繼承的使用
阿新 • • 發佈:2019-02-11
#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那樣的舊程式碼)
如果想重用基類的程式碼,有其他方法:委託,或者聚合,組合等。為什麼非要走繼承這條路才能重用?也許是這是從日常生活經驗獲得的思維定勢:繼承家產,就可以使用家產;繼承文化傳統,就可以使用這些知識了。