2009-04-17 110 views
1

我問了一個similar question yesterday,這是一種技術特有的,但現在我發現自己在廣泛意義上對這個主題感到疑惑。基於RTTI或基類修改的基礎或子類法案

爲了簡便起見,我們有兩個類,A和B,其中B是由A,B衍生真正的「是」 A,和所有A中所定義的例程具有相同的含義B.

假設我們想要顯示一個As列表,其中一些實際上是Bs。當我們遍歷As的列表時,如果當前對象實際上是一個B,我們想要顯示一些Bs附加屬性....或者我們只是想要區分Bs的顏色,但A和B都沒有任何概念「顏色」或「顯示內容」。

解決方案:

  1. 使A類由基本上包括一個稱爲ISB中的方法,()返回假半知道B的。 B將重寫該方法並返回true。顯示代碼將具有如下檢查:if(currentA.isB())B b = currentA;

  2. 在A中提供一個顯示()方法,B可以覆蓋....但是然後我們開始合併UI和模型。我不會考慮這個,除非有一些很酷的技巧我沒有看到。

  3. 使用instanceof來檢查,如果要顯示的對象的當前確實是一個B.

  4. 距離b。添加所有的垃圾,以A,即使它並不適用於A.基本上只包含A中的B(不從A繼承)並將其設置爲null,直到它適用。這有點吸引人。這與#1相似,我猜是有繼承的構成。

看起來這個問題似乎應該不時出現,並有一個明顯的解決方案。

所以我想這個問題,也許真的可以歸結爲:

如果我有一個由延伸的基類,添加新的功能(不只是改變了基類的現有行爲)的子類,是我在做一些悲慘的事情?當我們嘗試對可能是A或B的對象集合進行操作時,它似乎立即崩潰。

回答

2

選項2(或1和2的混合)的變體可能是有意義的:畢竟,多態性是「Bs是As但需要在情形X中表現不同的標準解決方案」。同意,display()方法可能會將模型綁定到UI過於密切,但可能在UI級別所需的不同渲染反映了模型級別的語義或行爲差異。那些可以用方法捕獲嗎?例如,它可以是一個getPriority()(例如)方法,而不是直接使用getDisplayColour()方法,A和B返回不同的值,但它仍然由UI決定如何將其轉換爲顏色?

鑑於你更普遍的問題,不過,「我們怎麼能處理額外行爲,我們不能或不會允許通過基類的多態訪問,」例如,如果基類ISN」在我們的控制下,你的選擇可能是選項3,訪問者模式或助手類。在這兩種情況下,您都有效地將多態性擴展到外部實體 - 在選項3中,UI(例如,演示者或控制器)執行instanceOf檢查,並根據它是否爲B來執行不同的事情;在訪客或助手的情況下,新的班級。以您的示例爲例,訪問者可能是過度殺傷性的(另外,如果您不能/不願意更改基類以適應它,就不可能實現它),所以我建議一個簡單的類像「渲染」:

public abstract class Renderer { 
    public static Renderer Create(A obj) { 
    if (obj instanceOf B) 
     return new BRenderer(); 
    else 
     return new ARenderer(); 
    } 

    public abstract Color getColor(); 
} 

// implementations of ARenderer and BRenderer per your UI logic 

此封裝運行時類型檢查和高達捆綁代碼到合理地定義的類與明確的職責,而無需觀衆的概念開銷。 (但是,根據GrizzlyNyo的回答,如果你的層次結構或功能集比你在這裏展示的要複雜得多,訪問者可能會更合適,但很多人發現訪問者很難找到他們的頭,我傾向於避免它簡單的情況 - 但你的里程可能會有所不同。)

+0

第二段的+1。這是需要區分的外部代碼,因爲兩個類不關心。一個班級責任,而您的班級(層級)責任不是選擇在窗口中使用的顏色。 – 2009-04-18 09:26:25

0

這看起來像Visitor設計模式(也稱爲「Double Dispatch」 )。

請參閱this answer以獲取關於訪問者和複合模式的詳細解釋的鏈接。

1

itowlson給出的答案涵蓋了問題的絕大部分。我現在將盡我所能處理最後一段。

繼承應該實現重用,因爲您的派生類可以在舊代碼中重用,而不是重用基類的某些部分(可以使用聚合)。從這個觀點來看,如果你有一個類在新的代碼上使用一些新的功能,但應該作爲一個前類透明地使用,那麼繼承就是你的解決方案。新代碼可以使用新功能,舊代碼將無縫地使用您的新對象。

雖然這是一般意圖,但還是有一些常見的瑕疵,這裏的線條很微妙,您的問題恰恰就是這條線。如果你有一個base類型的對象集合,那應該是因爲這些對象只能用於base的方法。他們是'基地',表現得像基地。

在C++中使用'instanceof'或downcasts(dynamic_cast <>())來檢測真正的運行時類型是我在代碼審查中標記的東西,並且只有在程序員詳細解釋了爲什麼後才接受其他選項比該解決方案更糟糕。我會接受它,例如,在itowlson的回答下,前提是信息不適用於基於給定的操作。也就是說,基本類型沒有任何方法可以爲調用者提供足夠的信息來確定顏色。如果包含這樣的操作是沒有意義的:除了prepresentation顏色,你是否會根據相同的信息對對象執行任何操作?如果邏輯取決於實際類型,那麼操作應該在基類中被派生類中的重寫。如果這是不可能的(操作是新的,並且只對於某些給定的子類型),那麼至少應該在基礎中進行操作,以允許調用者確定downcast不會失敗。然後再次,我真的需要一個合理的理由讓調用者代碼需要知道真實類型。爲什麼用戶想用不同的顏色來看它?用戶會對每種類型執行不同的操作嗎?

如果最終需要使用代碼繞過類型系統,那麼您的設計對它有一個奇怪的smell。當然,永遠不要說永不言敗,但你可以肯定地說:避免根據instance of或邏輯downcasts。

+0

在非常簡單的情況下,你所說的(「新功能使用新對象,開心!」)是真實的。現在考慮這個非常真實的場景:您實現了繼承舊基類「A」的新子類「X」。您將一個混合`X`和`A`的容器送入``OldMethod()`,它不能在`X`上操作,但是您希望將結果送入`NewMethod()`,它識別並使用'X `和`A`。 `NewMethod()`需要以某種方式區分`X`和`A`。 – kizzx2 2010-08-26 14:54:33