2012-08-02 139 views
4

我有一個(Java)類,WindowItem,有一個問題:其中一個方法不是線程安全的。我無法修復WindowItem,因爲它是外部框架的一部分。所以我想我爲它實現了一個裝飾器,它在所討論的方法上有一個「同步」關鍵字。最終方法的Java類裝飾器

裝飾器擴展WindowItem並且還包含WindowItem。在Decorator模式之後,我在Decorator中創建了調用它所包含的WindowItem的方法。

但是,WindowItem有幾個最終方法,我不能在裝飾器中重寫。這打破了裝飾者的透明度。讓我們更加明確:

public class WindowItem { 
    private List<WindowItem> windows; 

    public Properties getMethodWithProblem() { 
     ... 
    } 

    public final int getwindowCount() { 
     return windows.size(); 
    } 
} 

public class WindowItemDecorator extends WindowItem { 
    private WindowItem item; 

    public WindowItemDecorator(WindowItem item) { 
     this.item = item; 
    } 

    # Here I solve the problem by adding the synchronized keyword: 
    public synchronized Properties getgetMethodWithProblem() { 
     return super.getMethodWithProblem(); 
    } 

    # Here I should override getWindowCount() but I can't because it's final 
} 

在我自己的代碼,每當我有什麼地方傳遞WindowItem,我在一家裝飾包裹它首先:新WindowItemDecorator(項目) - 和線程安全問題就消失了。但是,如果我的代碼在WindowItemDecorator上調用getwindowCount(),它將始終爲零:它在超類上執行getWindowCount(),而不是「item」成員。

所以我會說WindowItem的設計(它具有公共final方法的事實)使得不可能爲這個類創建一個裝飾器。

這是正確的,還是我錯過了什麼?

在這種情況下,我可能會保留裝飾器中窗口列表的副本,並保持同步,然後getWindowCount()的結果將是正確的。但在這種情況下,我更喜歡叉和修補框架...

+0

你的第一個反射是正確的,但最終的方法使它無法覆蓋它們。你必須傳遞/操作「WindowItem」類型的對象嗎?如果不是,則不必使用裝飾模式。您可以使用組合並通過將它們包裝到不擴展WindowItem但通過組合使用實例的新類中來控制對WindowItem的最終方法的調用。 – Ushox 2012-08-02 10:08:02

回答

0

你的問題的答案是肯定的,你不能重寫最終方法,這意味着它不可能爲這個類創建一個裝飾器。

如果您可以重寫出現問題的方法,並通過同步方法來解決問題,則可以將其留在那個位置。也就是說,只需使用你的子類,而不要使用裝飾器模式。

+0

謝謝。如果我創建WindowItem對象,這是一個很好的解決方案。然後,我創建了一個SyncedWindowItem,解決了問題,而不是創建一個WindowItem對象。不幸的是,WindowItem對象是爲我創建的(它甚至不是具體的類,它是抽象的),所以我不能在已經存在的對象上這樣做。 – Paul 2012-08-02 09:47:02

1

也許你可以使用Delegation Pattern,如果WindowItem類實現了一個定義你所關心的所有方法的接口,這將很好地工作。或者,如果它不打破太多的現有代碼來引用這個委託類而不是WindowItem

+0

這個問題比這個更微妙 - 「WindowItem」沒有'interface',而是交給了'WindowItem's。 – fommil 2012-08-02 09:55:13

+0

我相信你所說的是爲WindowItem的接口創建裝飾器,而不是爲類本身。我同意這一點,但在這種情況下,沒有這樣的接口。 – Paul 2012-08-02 09:56:44

+0

@Paul Swing和一個小姐。世界上沒有足夠的界面嗎?! – 2012-08-02 10:04:40

-1

一位同事有一個想法,我認爲可以解決問題。我可以通過查看修改List窗口的所有方法來保持超類的狀態和「item」成員的狀態同步。有幾個:addWindow,removeWindow。而不是調用的只是 「item.addWindow(...)」 中的裝飾,我叫addWindow的超類:

普通裝飾:

public void addWindow(WindowItem newItem) { 
    item.addWindow(newItem); 
} 

在這種情況下,我做的事:

public void addWindow(WindowItem newItem) { 
    super.addWindow(newItem); 
    item.addWindow(newItem); 
} 

保持狀態同步並且最終方法的返回值正確。

這是一個可以工作或不工作的解決方案,具體取決於正在裝飾的類的內部。

+0

這不是一個通用的解決方案,但在這種情況下,我相信它是有效的,因爲如果內部狀態同步(在這種情況下!),我不需要重寫任何最終方法。請注意,最終的方法是getWindowCount()。如果我確定超類在「item」處具有相同數量的窗口,則不需要重寫getWindowCount()。 – Paul 2012-08-02 10:04:42

+0

所以基本上,你保留實例的兩個副本 - 一個作爲委託,另一個作爲超級 - 這樣一個方法返回正確的答案,另一個是所有其他方法的正確答案以及添加的線程安全邏輯?盡情享受吧! :-) – fommil 2012-08-02 10:10:31

+0

順便說一下,如果有兩個版本容易出現[競爭條件](http://en.wikipedia.org/wiki/Race_condition),你可能已經破壞了該類的線程安全性(假設它有任何) )。我相信任何超出你的控制範圍的東西都沒有提到原始的「WindowItem」? – fommil 2012-08-02 10:19:52

1

如何不以這種方式考慮問題?爲什麼不直接處理代碼中的線程問題,而沒有假設線程安全性爲WindowItem

// I personally prefer ReadWriteLocks, but this sounds like it will do... 
synchronized (windowItem) { 
    windowItem.getMethodWithProblem(); 
} 

然後與軟件包維護人員一起提交RFE以更好地支持線程安全。

事實上,如果該類不是線程安全的,幾個​​關鍵字不太可能真正解決問題。究竟是什麼人的意思是「線程安全」永遠是相對的;-)

(順便說一句,WindowItem絕對是線程安全的,因爲它是用List而是明確使用的「螺紋準備」變種Correct way to synchronize ArrayList in java - 也有不保證以線程安全的方式訪問List)。

+1

...並要求他們在實現界面時也實現一個界面。它從來不會傷害(特別是在單元測試中嘲笑)。 – 2012-08-02 10:08:19

+0

如果'interface'太重,那麼至少從這樣一個簡單的方法中刪除'final'修飾符。或者,至少是一致的http://stackoverflow.com/questions/218744 – fommil 2012-08-02 10:14:02

+0

儘管使用裝飾器解決這個問題的關鍵是我在一個地方解決了這個問題,而不是必須修復WindowItem的所有用法我的代碼。 – Paul 2012-08-02 10:18:54