2010-08-30 67 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可以使用策略模式进行自定义,但我不确定那会给我带来什么。

我很乐意听到您(或其他任何人,当然)想到这种方法。