我让自己陷入两个班级之间的循环依赖,我正试图想出一个干净的解决方案。
这是基本结构:
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需要的问题在其ctor中有RM,而RM需要在其ctor中使用CM。
显然,这不起作用,所以我试图提出创造性的解决方案。到目前为止,我有:
1)传递给ReportNewItem要使用的Container,让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 :(得分:2)
您正在寻找的是界面。接口允许您将共享对象的结构/定义提取到外部引用,允许它独立于Container
和ResourceManager
类进行编译,并且两者都不依赖。
创建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
答案 1 :(得分:0)
解决方案5怎么样,但容器派生自一个实现你提到的跨容器逻辑的公共基类?
答案 2 :(得分:0)
只有你的短片段(一个必需的约束,我敢肯定 - 但很难知道ResourceManager是否可以变成一个单例。)这是我的快速想法
1)调用ReportNewItem()
时,你能不能只将项目所在的容器传递给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()
,我喜欢将它放在工厂中以分离关注原因。
答案 3 :(得分:0)
谢谢大家的答案。
jalexiou - 我将调查KeyedCollection,谢谢(谢谢,我真的需要注册,所以我可以发表评论)。
詹姆斯,正如我写的那样,我确实想要使用接口(如果没有别的话,它简化了单元测试)。我的问题是初始化实际的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);
这对我来说似乎很干净。希望没有人不同意:))
我最初反对传递Component(或者实际上类似我在这里提出的访问器)的原因是我必须在初始化时将它们重新提供给ResourceManager,因为ResourceManager会持久存储它拥有的Items。但事实证明我必须使用Items重新初始化它,所以这不是问题。
再次感谢您的讨论!
答案 4 :(得分:0)
詹姆斯
是的,ComponentManager和ContainerManager是同一个(我的真实代码中的名称完全不同,我试图为代码片段选择“通用”名称 - 我让他们感到困惑)。如果您认为有任何其他细节会有所帮助,请告诉我,我会提供给他们。我试图让代码片保持简洁。
您是正确的,ComponentManager没有直接参与Component / ResourceManager关系。我的问题是我希望能够使用不同的ResourceManager进行测试。实现这一目标的一种方法是让CM为组件提供RM(实际上,只有一个RM,因此它必须由除每个组件之外的其他人构建)。
除了隐藏我不希望ResourceManager知道的Component部分(同时允许使用ComponentAccessorMock测试ResourceManager)之外,ComponentAccessor几乎没有什么作用。通过让Component实现一个只暴露我希望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可自定义,但我不确定这会给我带来什么。
我很高兴听到你(或其他任何人)当然会想到这种方法。