[OOD]違反里氏替換原則的解決方案
關於OOD中的里氏替換原則,大家耳熟能祥了,不再展開,可以參考設計模式的六大設計原則之里氏替換原則。這裡嘗試討論常常違反的兩種形式和解決方案。
違反里氏替換原則的根源是對子類及父類關係不明確。我們在設計繼承關係常常受一些主觀認識的左右,比如Robert C. Martin提到的線段與線的關係,以及被大家說到爛的正方形與矩形。從以前的經驗我們認為它們符合繼承關係,比如線段是線的較短形式,正方形是矩形的一個特例。但事實上它們並不能完全的包容和替代。
以集合的形式表示,左圖是里氏替換的目標,子類可以完全包容了父類的特性集合。右圖則是說兩者存在不相容的特性集合:
對應的解決方案就是進一步抽象,將它們之前的關係從語言的角度重新定義,也許果真是is-a, 也許是has-a,也許它們只是兄弟。
基本的思路如下:
1. 找到更高層次的抽象
以Robert C. Martin舉的線與線段為例, 初始實現Line是LineSegment的基類:
// Line代表經過兩點(P1,P2)的一條的直線
class Line{
public:
double GetSlope() const;
Point GetP1() const;
Point GetP2() const;
virtual bool IsOn(const Point&) const;
private:
Point itsP1;
Point itsP2;
}
// LineSegment則是由兩點(P1,P2)連線的線段。
class LineSegment : public Line {
public:
virtual bool IsOn(const Point&) const;
}
其中IsOn函式用於計算某個點在不在直線或線段上。對於直線而言,一個點在不在其上僅僅取決於這個點相對於直線的兩個點的關係。而對於線段而言,還是它是否線上程起止邊界內。兩者對於這個介面函式的判斷條件並不相同,所以LineSegment無法直接代替父類,違反了里氏替換原則。
解決方案是將這個不一致的介面排除掉,剩下的公共介面做為直線和線段的基類,即定義一個LinearObject做為Line及LineSegment的基類:
class Line{
public:
double GetSlope() const;
Point GetP1() const;
Point GetP2() const;
// 純虛擬函式的意義在於,確保使用基類的客戶程式碼不會使用這個介面函式
virtual bool IsOn(const Point&) const = 0;
private:
Point itsP1;
Point itsP2;
}
2. 改為has-a關係
另一種解決方案,是針對繼承關係太過牽強的情況,比如所謂的is-implemented-in-terms-of (由誰實現)的情況,不如轉化為組合模式,如下面的關係:
Scott Meyers在Effective C++ 3e, Item 38提到一個案例。比如準備基於std::list實現一個Set。初步想法是期望保持與list相同的介面,於是定義為:
template<typename T>
class Set : public std::List<T> {...}
但Set與List在行為有一個巨大的差異是Set不允許重複的元素,所以也違反了里氏替換原則。
解決方案就是,使用std::list實現,就是一個has-a關係,可以定義為:
template<typename T>
class Set {
public:
void insert(const T& item);
void remove(const T& item);
...
private:
std::list<T> rep;
}
相關文章
- 里氏替換原則
- Laravel深入學習10 – 里氏替換原則Laravel
- DesignPattern系列__04里氏替換原則
- 設計模式的七大原則(4) --里氏替換原則設計模式
- 嘻哈說:設計模式之里氏替換原則設計模式
- S.O.I.L.D 之里氏替換原則
- 物件導向設計的六大原則(SOLID原則)-——里氏替換原則物件Solid
- 軟體開發六大原則(三)-里氏替換原則
- 面象物件設計6大原則之三:里氏替換原則物件
- 設計模式例項講解 - 里氏替換設計模式
- 設計原則之【裡式替換原則】
- 【解決方案】專案重構之如何使用 MySQL 替換原來的 MongoDBMySqlMongoDB
- The Principles of OOD 物件導向設計原則物件
- 設計模式六大原則(二)----裡式替換原則設計模式
- 翻譯 | The Principles of OOD 物件導向設計原則物件
- 如何權衡業務規則的遵守與違反?
- vim表示式正則替換
- 全域性替換 ‘/home’ 為 ‘/’ 的問題解決!
- 設計原則-依賴反轉原則
- 正則替換 修改字元 去除空格字元
- 設計原則之【依賴反轉原則】
- 如何快速解決繁雜的國際化替換
- .NET正則替換URL引數值
- App\User 替換為 App\Models\User 的問題解決!APP
- 蘋果被開發者“抓包”違反自己定的規則蘋果
- 反網路爬蟲以及解決方案爬蟲
- sql 正則替換資料庫語句!SQL資料庫
- Microsoft.AspNet.Web.Optimization.Bundle的完美替換方案ROSWeb
- 全網最適合入門的物件導向程式設計教程:10 類和物件的 Python 實現-類的繼承和里氏替換原則,Python 模擬主機和感測器自定義類物件程式設計Python繼承
- 基於多重替換方式的iOS程式碼混淆方案iOS
- Nginx的location規則:優先順序和路徑替換Nginx
- HTML 替換元素與非替換元素HTML
- 域名被反詐中心攔截無法開啟的解決方案
- JS和C#實現的兩個正則替換功能示例分析JSC#
- Vi替換
- 替換空格
- 優思學院|六西格瑪改進階段中選擇解決方案的原則和技巧
- 換IP經常出現的問題及其解決方案
- 原碼,反碼,補碼相互轉換