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 需要知道创建的新项目。它进行后台处理,有时它需要来自项目容器的信息。

现在,Container 需要有 ResourceManager(调用 ReportNewItem),它将从 ContainerManager 传递。ResourceManager 需要来自 Container 的信息,它只能通过 ContainerManager 获取。这会产生循环依赖。

我更喜欢使用接口(而不是具体对象)初始化对象,以便以后可以为单元测试创​​建模拟对象(例如创建模拟 ResourceManager),但我仍然遇到 CM 需要 RM 的问题它的 ctor,而 RM 的 ctor 中需要一个 CM。

显然,这是行不通的,所以我正在尝试提出创造性的解决方案。到目前为止,我有:

1)将要使用的Container传递给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-specific 逻辑,并在每个 Container 中拥有不同的实例。不幸的是,逻辑确实是跨容器的。

我认为“正确”的方法是将一些代码提取到 CM 和 RM 都依赖的第三类中,但我想不出一个优雅的方法来做到这一点。我想出了要么封装“报告项目”逻辑,要么封装组件信息逻辑,这两种方法似乎都不对。

任何见解或建议将不胜感激。

4

5 回答 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();

将此概念扩展到您的每个循环引用。


* 更新 *

您不需要将所有依赖项删除到一个接口中......这会非常好,例如,让 aResourceManager了解/依赖于 aContainer

于 2010-08-30T20:49:59.953 回答
0

詹姆士,

是的,ComponentManager 和 ContainerManager 是一回事(我的真实代码中的名称完全不同,我试图为代码片段选择“通用”名称 - 我把它们弄糊涂了)。如果您认为有任何其他细节会有所帮助,请告诉我,我会提供。我试图保持片段简洁。

您是正确的, ComponentManager 不直接参与 Component/ResourceManager 关系。我的问题是我希望能够使用不同的 ResourceManager 进行测试。实现此目的的一种方法是让 CM 将 RM 提供给组件(实际上,只有一个 RM,因此它必须由每个组件以外的其他人构建)。

除了隐藏我不想让 ResourceManager 知道的 Component 部分(同时允许使用 ComponentAccessorMock 测试 ResourceManager)之外,ComponentAccessor 几乎没有做任何事情。同样的事情可以通过让组件实现一个接口来实现,该接口只公开我希望 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 可定制,但我不确定这会给我带来什么。

我很高兴听到您(或其他任何人,当然)对这种方法的看法。

于 2010-09-01T11:56:48.363 回答
0

只需您的简短片段(我敢肯定,这是一个必需的约束 - 但很难知道 ResourceManager 是否可以变成单例。)这是我的快速想法

1)ReportNewItem()被调用的时候,能不能不直接把item所在的容器传给ResourceManager?这样,RM 就不需要接触 containermanager。

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(),出于关注点分离的原因,我喜欢将它放在工厂中。

于 2010-08-30T20:57:09.937 回答
0

解决方案 5 怎么样,但是让容器派生自实现您提到的跨容器逻辑的公共基类?

于 2010-08-30T20:48:23.710 回答
0

谢谢大家的回答。

jalexiou - 我会调查 KeyedCollection,谢谢(天哪,我真的需要注册才能发表评论)。

James,正如我所写,我确实想使用接口(如果没有别的,它会简化单元测试)。我的问题是,要初始化实际的 ResourceManager,我需要传递 ComponentManager,而要初始化 CM,我需要传递 RM。您建议的基本上是两阶段初始化,我将其称为解决方案 2。我宁愿避免这种两阶段初始化,但也许我在这里过于虔诚。

Philip,我认为将 Component 传递给 ReportNewItem 会对 ResourceManager 暴露太多(因为 Component 支持各种我不希望 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 重新初始化它,所以这不是问题。

再次感谢您的精彩讨论!

于 2010-08-31T04:58:41.117 回答