【发布时间】:2010-08-30 20:37:10
【问题描述】:
我让自己陷入了两个类之间的循环依赖,我正在尝试提出一个干净的解决方案。
这是基本结构:
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 需要的问题其 ctor 中有一个 RM,而 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 的逻辑,并在每个 Container 中具有不同的实例。不幸的是,逻辑确实是跨容器的。
我认为“正确”的方法是将一些代码提取到 CM 和 RM 都依赖的第三类中,但我想不出一个优雅的方法来做到这一点。我想出了要么封装“报告项”逻辑,要么封装组件信息逻辑,这两种方法似乎都不对。
任何见解或建议将不胜感激。
【问题讨论】:
-
您是否考虑过使用
System.Collections.ObjectModel命名空间中的KeyedCollection<TKey, TItem>。它将帮助您简化 item 和 itemID 或 container 和 containerID 之间的类层次结构。 -
还可以考虑使用事件而不是将函数调用链接到
DoStuff()和ReportNewItem()
标签: c# oop circular-dependency