2010-08-30 55 views
0

我把自己變成了兩個類之間的循環依賴關係,我試圖想出一個乾淨的解決方案。設計問題 - 解決對象之間的循環依賴關係

這裏的基本結構:

class ContainerManager { 
    Dictionary<ContainerID, Container> m_containers; 

    void CreateContainer() { ... } 
    void DoStuff(ContainerID containerID) { m_containers[containerID].DoStuff(); } 
} 

class Container { 
    private Dictionary<ItemID, Item> m_items; 

    void SetContainerResourceLimit(int limit) { ... } 

    void DoStuff() { 
     itemID = GenerateNewID(); 
     item = new Item(); 
     m_items[itemID] = item; 
     // Need to call ResourceManager.ReportNewItem(itemID); 
    } 
} 

class ResourceManager { 
    private List<ItemID> m_knownItems; 

    void ReportNewItem(ItemID itemID) { ... } 

    void PeriodicLogic() { /* need ResourceLimit from container of each item */ } 
} 

的ContainerManager公開爲WCF服務:通過客戶端可以創建項目和容器外點。 ResourceManager需要知道創建的新項目。它做後臺處理,偶爾它需要來自Item的容器的信息。

現在,Container需要有ResourceManager(要調用ReportNewItem),它將從ContainerManager傳遞。 ResourceManager需要來自Container的信息,它只能使用ContainerManager獲取。這創建了一個循環依賴。我傾向於使用接口(而不是具體對象)初始化對象,以便稍後可以爲單元測試創​​建模擬對象(例如創建一個模擬的ResourceManager),但是我仍然留下了CM的問題RM需要RM,而RM需要CM​​的CM。

很明顯,這是行不通的,所以我試圖想出創造性的解決方案。到目前爲止,我有:

1)傳遞給ReportNewItem要使用的容器,並讓ResourceManager直接使用它。這是一個痛苦,因爲ResourceManager持久地存儲它所知道的ItemID。這意味着,在例如崩潰之後初始化ResourceManager時,我將不得不重新提供它所需的所有容器。 2)以兩個階段初始化CM或RM:例如:RM = new RM(); CM =新CM(RM); RM.SetCM(CM);但我認爲這很難看。

3)使ResourceManager成爲ContainerManager的成員。因此,CM可以用「this」構造RM。這將起作用,但在測試期間我會想創建一個RM模擬器時會很痛苦。

4)用IResourceManagerFactory初始化CM。讓CM調用Factory.Create(this),它將使用「this」初始化RM,然後存儲結果。爲了測試,我可以創建一個模擬工廠,它將返回一個模擬RM。我認爲這將是一個很好的解決方案,但爲此創建工廠有點尷尬。

5)將ResourceManager邏輯分解爲特定於Container的邏輯,並在每個Container中具有不同的實例。不幸的是,邏輯真的是跨容器。

我認爲「正確的」方法是將一些代碼放到CM和RM都依賴的第三類中,但我無法想出一個優雅的方法來實現這一點。我想出了封裝「報告的項目」邏輯,或封裝組件信息邏輯,這兩者似乎都沒錯。

任何見解或建議將不勝感激。

+0

您是否考慮過使用'System.Collections.ObjectModel'命名空間中的'KeyedCollection '。它將幫助您簡化item和itemID或container和containerID之間的類層次結構。 – ja72 2010-08-30 20:47:30

+0

另外考慮使用事件而不是將函數調用鏈接到'DoStuff()'和'ReportNewItem()' – ja72 2010-08-30 20:51:26

回答

0

解決方案5如何,但容器派生自實現您提到的跨容器邏輯的公共基類?

2

你要找的是一個界面。通過一個接口,可以將共享對象的結構/定義提取到外部引用,從而使其可以獨立於ContainerResourceManager類進行編譯,也不依賴於它們。

當您創建Container時,您將擁有想要容器報告的ResourceManager ...將其傳遞給構造函數,或將其設置爲屬性。

public interface IResourceManager { 
    void ReportNewItem(ItemID itemID); 
    void PeriodicLogic(); 
} 


public class Container { 
    private Dictionary<ItemID, Item> m_items; 

    // Reference to the resource manager, set by constructor, property, etc. 
    IResourceManager resourceManager; 

    public void SetResourceManager (IResourceManager ResourceManager) { 
     resourceManager = ResourceManager; 
    } 

    public void DoStuff() { 
     itemID = GenerateNewID(); 
     item = new Item(); 
     m_items[itemID] = item; 
     resourceManager.ReportNewItem(itemID); 
    } 
} 


public class ResourceManager : IResourceManager { 
    private List<ItemID> m_knownItems; 

    public void ReportNewItem(ItemID itemID) { ... } 
    public void PeriodicLogic() { ... } 
} 


// use it as such: 
IResourceManager rm = ResourceManager.CreateResourceManager(); // or however 
Container container = new Container(); 
container.SetResourceManager(rm); 
container.DoStuff(); 

將此概念擴展到您的每個循環引用。


*更新*

你並不需要所有的依賴取出放入接口......這將是完全正常的,例如,對於一個ResourceManager瞭解/取決於一個Container

0

只要輸入您的一小段(所需的約束,我敢肯定 - 但很難知道是否ResourceManager中也可以變成例如單)這是我最新的想法

1)ReportNewItem()被調用,您是否可以不只是將項目所在的容器傳遞給ResourceManager?這樣,RM不需要觸摸容器管理員。

class Container { 
    private IResourceManager m_rm; //.. set in constructor injection or property setter 

    void DoStuff() { 
     itemID = GenerateNewID(); 
     item = new Item(); 
     m_items[itemID] = item; 
     m_rm.ReportNewItem(this, itemId); 
    } 
} 

class ResourceManager { 
    private List<ItemID> m_knownItems; 
    private Dictionary<ItemID, Container> m_containerLookup;   

    void ReportNewItem(Container, ItemID itemID) { ... } 

    void PeriodicLogic() { /* need ResourceLimit from container of each item */ } 
} 

2)我是工廠的粉絲。一般來說,如果對一個類的正確實例進行構造或檢索不僅僅是new(),我喜歡將它放在一個工廠中以分離關注的原因。

0

謝謝大家的答案。

jalexiou - 我會看看KeyedCollection,謝謝(sheesh,我真的需要註冊,所以我可以發表評論)。我寫了James,我確實想要使用接口(如果沒有別的,它會簡化單元測試)。我的問題是,初始化實際的ResourceManager,我需要通過ComponentManager,並初始化CM我需要通過RM。你建議的基本上是一個兩階段的初始化,我稱之爲解決方案2.我寧願避免這種兩階段初始化,但也許我在這裏過於虔誠。菲利普,我認爲將組件傳遞給ReportNewItem會暴露給ResourceManager太多(因爲組件支持我不想訪問ResourceManager的各種操作)。

然而,再考慮這件事,我可以採取以下方法:

class ComponentManager { ... } 

class Component { 
    private ComponentAccessorForResource m_accessor; 
    private ResourceManager m_rm; 

    Component(ResourceManager rm) { 
     m_accessor = new ComponentAccessorForResource(this); 
     m_rm = rm; 
    } 
    void DoStuff() { 
     Item item = CreateItem(); 
     ResourceManager.ReportNewItem(item.ID, m_accessor); 
    } 
    int GetMaxResource() { ... } 
} 

class ComponentAccessorForResource { 
    private Component m_component; 
    ComponentAccessorForResource(Component c) { m_component = c; } 
    int GetMaxResource() { return m_component.GetMaxResource(); } 
} 

ResourceManager rm = new ResourceManager(); 
ComponentManager cm = new ComponentManager(rm); 

這似乎不夠乾淨給我。希望沒有人不同意:)

我最初反對傳遞組件(或者實際上類似於我在此提出的訪問器),因爲ResourceManager在初始化時必須重新提供它們,因爲ResourceManager持久地存儲它有的項目。但事實證明,我必須用Items重新初始化它,所以這不是問題。

再次感謝您的良好討論!

+0

我有點困惑,因爲我們從ContainerManager和Container切換到ComponentManager和Component ...假設他們'同樣的事情。 您所提供的代碼中缺少很多細節...我能說的最好的是ComponentManager似乎是不必要的,或者至少不涉及Component/ResourceManager關係。 組件知道它的ResourceManager ...到目前爲止,這是有道理的。 ResourceManager知道組件,因爲它適用於這些組件。還好。 (更多) – 2010-08-31 14:51:25

+0

如果ComponentManager有必要管理組件,好吧,但沒有理由在組件和ResourceManager之間進行調用。 沒有CM知道RM可以解決您的循環依賴問題。 我不清楚什麼是ComponentAccessor給你。它看起來像一個不必要的包裝,只存儲一個組件,並且包裝一個函數而不添加任何值;你可能只是公開Component.GetMaxResource。 如果您對我的建議不滿意,我仍鼓勵您多思考,想出另一種方法 – 2010-08-31 14:56:52

0

詹姆斯,

是,的ComponentManager和ContainerManager是同一個(在我真正的代碼名稱是完全不同的,我是想選擇的代碼片斷「通用」的名字 - 我得到了他們困惑)。如果有其他細節您認爲會有幫助,請告訴我,我會提供。我試圖保持片段簡潔。

您是正確的,ComponentManager不直接涉及Component/ResourceManager關係。我的問題是我希望能夠使用不同的ResourceManager進行測試。實現這一目標的一種方法是讓CM向組件提供RM(實際上,只有一個RM,所以它必須由除每個組件之外的其他人構建)。

ComponentAccessor除了隱藏我不想讓ResourceManager知道的Component的各個部分之外,幾乎沒有什麼其他功能(同時允許使用ComponentAccessorMock測試ResourceManager)。同樣的事情可以通過讓組件實現一個接口來公開我想讓RM使用的方法。這實際上是我在我的代碼中做的,我懷疑這是你通過「公開Component.GetMaxResource」引用的內容。

代碼現在看起來大致像這樣:

// Initialization: 

RM = new RM(); 
CM = new CM(RM); // saves RM as a member 

// 
// Implementation 
// 

// ComponentManager.CreateComponent 
C = new Component(m_RM); // saves RM as a member 

// Component.CreateNewItem 
{ 
    Item item = new Item(); 
    m_RM.ReportNewItem(this, item); 
} 

而且ReportNewItem預計暴露它需要的方法的接口。這對我來說似乎相當乾淨。

一種可能的選擇是使ResourceManager可以使用策略模式進行自定義,但我不確定那會給我帶來什麼。

我很樂意聽到您(或其他任何人,當然)想到這種方法。