2009-04-09 49 views
2

我這個昨天寫在一類Foo從酒吧繼承:「擁有」與「是」 - 代碼味道來決定

public override void AddItem(double a, int b) 
{ 
    //Code smell? 
    throw new NotImplementedException("This method not usable for Foo items"); 
} 

納悶隨後,如果這是一個可能的跡象,我應該使用一個Bar,而不是從它繼承。

什麼其他'代碼氣味'可以幫助選擇繼承和組成?

編輯我要補充一點,這是一個片段,還有其它方法是常見,我只是不想去考慮太多的細節。我必須分析轉換爲合成的含義,並想知道是否可能存在其他可能有助於平衡的「代碼氣味」。

回答

17

上面給出的例子顯然是代碼味道。 AddItem方法是基類Bar的行爲。如果Foo不支持AddItem行爲,則不應繼承Bar

讓我們來思考一個更現實的(C++)示例。比方說,你有以下類:

class Animal 
{ 
    void Breathe() const=0; 
} 

class Dog : public Animal 
{ 
    // Code smell 
    void Breathe() { throw new NotSupportedException(); } 
} 

基礎抽象類Animal提供了一個純虛Breathe()方法,因爲動物必須呼吸才能生存。如果它沒有呼吸,那麼根據定義它不是動物。

通過創建一個新的類DogAnimal繼承,但支持Breathe()行爲,你正在打破由Animal類規定的合同。可憐的狗將無法生存!

公共繼承的簡單規則是,如果派生類對象確實是「基類」對象,那麼您應該只做它。

在你的具體的例子:

  • Foo不支持由Bar合同規定的AddItem()行爲。
  • 因此,根據定義,Foo是「不是」Bar,不應該從它繼承。
+0

我不知道NotImplementedException是否是一種代碼異味 - 異常表明它沒有實現_yet_。如果它是一個NotSupportedException,我同意。 – Lennaert 2009-04-09 06:59:42

+0

同意。考慮到我的懶惰 - 我只是複製並粘貼了原來的問題。 – LeopardSkinPillBoxHat 2009-04-09 07:07:53

+0

我修改了我對NotSupportedException的回答。 – LeopardSkinPillBoxHat 2009-04-09 07:21:57

3

那麼,爲什麼你會繼承自Bar,如果沒有擴展名的Foo的行爲不像Bar? 想想吧,我甚至不會在基類中聲明像'AddItem'這樣的虛擬方法。

+0

想想富行爲90%喜歡酒吧。而客戶端代碼只使用這90%的行爲。就像使用java.util.List但不修改它的代碼一樣,只能通過索引迭代並獲取它的運行結果。然後你可以給它一個子類,其中設置,添加,刪除和清除方法沒有實現。 – 2009-04-16 21:10:18

+1

帕維爾:當有人要實際使用的其他10%和編譯器不阻止他,因爲你已經顛覆了靜態類型檢查安全網的問題就來了。 – 2009-12-10 22:01:05

1

當然,如果你的Foo實現了很多隻將消息轉發給它擁有的Bar的接口,這可能表明它應該是一個Bar。儘管如此,氣味並不總是正確的。

3

是的,你必須「不執行」的方法是一個跡象表明,也許你不應該使用「是」的關係。你的Foos似乎並不真的是酒吧。

但首先想想你的Foos和酒吧。 Foos酒吧?你可以在紙上繪製集合和子集,並且每個Foo(即每個Foo類的成員)也都是一個Bar(即Bar類的成員)嗎?如果不是,你可能不想使用繼承。

另一個代碼的氣味,指出FOOS不是真正的酒吧,和富不應該繼承吧,是你不能使用多態。假設你有一個方法將Bar作爲參數,但它不能處理Foo。 (也許是因爲它在它的參數中調用了AddItem方法!)你必須添加一個檢查,或者處理NotImplementedException,這使得代碼變得複雜而難以理解。 (和聞!)

3

沒有!一個正方形可能是一個矩形,但是 一個正方形對象絕對不是 矩形對象。爲什麼?因爲Square對象的 行爲不是 ,與 Rectangle對象的行爲一致。 行爲上, 正方形不是矩形!並且它是 行爲那個軟件真的全是 一下。

從對象嚮導中的The Liskov Substitution Principle

0

.NET Framework中有這樣的例子,尤其是在System.IO命名空間 - 一些讀者沒有實現他們所有的基類的屬性/方法,如果你嘗試使用它們會拋出異常。

E.g.流有位置屬性,但有些流不支持這一點。

1

雖然上面的例子可能表明出現了問題,但NotImplementedException本身始終沒有錯。這完全是關於超類的合同以及實施這個合同的子類。如果你的超類有這樣的方法

// This method is optional and may be not supported 
// If not supported it should throw NotImplementedException 
// To find out if it is supported or not, use isAddItemSupported() 
public void AddItem(double a, int b){...} 

然後,不支持這種方法仍然可以與合同。如果不支持,您可能應該禁用UI中的相應操作。所以如果你對這樣的合約可以,那麼子類就可以正確地實現它。

當客戶明確聲明,它不使用所有類的方法,並永遠不會另一種選擇。像這樣

// This method never modifies orders, it just iterates over 
// them and gets elements by index. 
// We decided to be not very bureaucratic and not define ReadonlyList interface, 
// and this is closed-source 3rd party library so you can not modify it yourself. 
// ha-ha-ha 
public void saveOrders(List<Order> orders){...} 

然後可以通過不支持添加,刪除和其他增變器的List實現。只需記錄它。

// Use with care - this implementation does not implement entire List contract 
// It does not support methods that modify the content of the list. 
public ReadonlyListImpl implements List{...} 

雖然有你的代碼來定義所有的合同是好的,因爲它讓你的編譯器檢查,如果你違反了合同,有時是不合理的,你必須訴諸弱定義的合同,這樣的評論。

總而言之一句話說到這個問題,如果你真的可以放心地使用你的子類的父類,考慮到超類是由它的合同,不僅是代碼中定義。