2010-03-31 49 views
5

我一直在讀這個「demeter法」的東西,它(和純粹的「包裝」類一般)似乎通常是反模式。考慮一個實現類:包裝/ demeter定律似乎是反模式

class FluidSimulator { 
    void reset() { /* ... */ } 
} 

現在考慮另一個類的兩種不同的實現:

class ScreenSpaceEffects1 { 
    private FluidSimulator _fluidDynamics; 
    public FluidSimulator getFluidSimulator() { return _fluidDynamics; } 
} 

class ScreenSpaceEffects2 { 
    private FluidSimulator _fluidDynamics; 
    public void resetFluidSimulation() { _fluidDynamics.reset(); } 
} 

,並調用方式表示方法:

callingMethod() { 
    effects1.getFluidSimulator().reset(); // Version 1 
    effects2.resetFluidSimulation();  // Version 2 
} 

乍一看,第2版似乎更簡單一些,並遵循「Demeter規則」,隱藏Foo的實現等等,但是這將FluidSimulator中的所有更改都聯繫到了ScreenSpaceEffects。例如,如果一個參數添加的復位,然後我們有:

class FluidSimulator { 
    void reset(bool recreateRenderTargets) { /* ... */ } 
} 

class ScreenSpaceEffects1 { 
    private FluidSimulator _fluidDynamics; 
    public FluidSimulator getFluidSimulator() { return _fluidDynamics; } 
} 

class ScreenSpaceEffects2 { 
    private FluidSimulator _fluidDynamics; 
    public void resetFluidSimulation(bool recreateRenderTargets) { _fluidDynamics.reset(recreateRenderTargets); } 
} 

callingMethod() { 
    effects1.getFluidSimulator().reset(false); // Version 1 
    effects2.resetFluidSimulation(false);  // Version 2 
} 

在這兩個版本,callingMethod需要改變,但在第2版,ScreenSpaceEffects 需要改變。有人可以解釋具有包裝​​器/外觀的優點(除適配器外或包裝外部API或暴露內部API)。

編輯:其中一個我碰到這個,而不是一個微不足道的例子的實例。

+0

你的意思是「版本2似乎更簡單」? – 2010-03-31 06:54:07

+0

是的,很抱歉,會改變 – 2010-03-31 06:57:14

+0

版本1不遵循德米特的規則。輸錯? – Corwin 2010-03-31 06:58:24

回答

13

主要區別在於,在版本1中,作爲Bar抽象的提供者,您無法控制Foo的公開方式。 Foo中的任何更改都將暴露給您的客戶,他們將不得不忍受它。

對於版本2,作爲抽象的提供者Bar,您可以決定是否以及如何公開演變。它僅取決於Bar抽象,而不取決於Foo。在你的例子中,你的Bar抽象可能已經知道哪個整數作爲參數傳遞,因此你將能夠讓你的用戶透明地使用新版本Foo,完全沒有任何改變。

假設現在Foo發展,並且要求用戶在致電doSomething之前致電foo.init()。在版本1中,Bar的所有用戶都需要看到Foo發生了變化,並調整了他們的代碼。對於版本2,只有Bar必須更改,其doSomething如果需要,則調用init。這將導致更少的錯誤(只抽象Bar有權知道和了解類之間抽象Foo少耦合的作者。

+0

我不相信這值得額外的複雜性(特別是如果類已經耦合),但感謝無論如何答案:-)。 – 2010-04-02 00:37:24

+0

幾乎任何時候都會發生變化,其他事情將不得不改變。最好的做法是儘量安排一切,以便即使可能發生變化的事情,即使可能會發生變化,那些希望保持不變的部分也能保持不變。每個給定的設計將能夠比其他設計更容易地適應某些潛在的未來API變化;哪種設計更好取決於如何判斷各種可能的未來變化的可能性。 – supercat 2013-11-11 23:03:53

2

這顯然是一個人爲的例子。在許多現實情況下,callingMethod(在現實生活中,可以有多個如果我使用穩定的打印API,我不必擔心我的打印機固件增加了對光面打印的支持。我現有的黑色我認爲你會將它歸入「適配器」下面,我認爲它比你暗示的要普遍的多。

你說得對,有時候調用方法一定是變也是。但是,當德米特法得到正確使用時,這種情況很少發生,通常利用新功能(與新界面不同)。

編輯:callMethod似乎很有可能不在乎是否重新創建渲染目標(我假設這是一個性能與準確性的問題)。畢竟,「我們應該忘記小效率,大約97%的時間」(Knuth)。因此,ScreenSpaceEffects2可以添加一個resetFluidSimulation(bool)方法,但通過在幕後調用_fluidDynamics.reset(true)resetFluidSimulation()繼續工作(不更改爲調用方法)。

+0

其實,我在問這個問題,因爲它在我的愛好項目中出現過很多次 – 2010-03-31 07:05:52

+1

出現了什麼問題?也許是一個真實的例子,而不是所有的'Foo'這個和'Bar'那個? – 2010-03-31 07:07:54

+1

增加了一個真實的例子。 – 2010-03-31 10:30:07

2

問題是,callingMethod()是否需要知道是否重新創建渲染表?

假設callingMethod()的一些給定的執行是否需要重新創建渲染表。在這種情況下,你用一種新方法擴展包裝。然後,您只需從callingMethod()的適當實例調用新方法即可。

class ScreenSpaceEffects2 { 
    private FluidSimulator _fluidDynamics; 
    public void resetFluidSimulation() { _fluidDynamics.reset(false); } 
    public void resetFluidSimulationWithRecreate() { _fluidDynamics.reset(true); } 
} 

或者重建可能屬於其他地方完全決定...

class ScreenSpaceEffects2 { 
    private FluidSimulator _fluidDynamics; 
    public void resetFluidSimulation() { 
      _fluidDynamics.reset(someRuleEngine.getRecreateRenderTables()); } 
} 

...在界河的情況下沒有在callingMethod()需要在所有改變。