我有一个上下文对象,它有几个有用的属性和方法。其他对象在其构造函数中接受此上下文对象。但是,上下文对象可以将这些其他对象作为公共属性。见下文:
public class Foo {
private IContext Context { get; private set; }
public Foo( IContext context ) {
Context = context;
}
}
public class SomeContext : IContext {
public Foo SomeProperty { get; set; }
/*
Other useful properties and methods as defined in IContext
*/
public SomeContext () {
SomeProperty = new Foo( this );
}
}
现在我可以在Foo上的方法中做一些有用的事情:
public void FooIt() {
IUseful bar = this.Context.GetUsefulInterface();
bar.DoUsefulThing();
}
然而,它可能导致一些非常奇怪的东西。在Foo上考虑这些方法:
public void DoSomething() {
/* useful stuff */
}
public void DoSomethingElse() {
this.Context.SomeProperty.DoSomething(); // could just call this.DoSomething();
this.Context.SomeProperty.DoSomethingElse(); // stack overflow!
}
这被认为是一个糟糕的设计/代码味道?上下文对象的原因有所涉及,我想将问题更多地放在周期性参考上。如果认为不好,打破这种周期性关系有哪些方法?
答案 0 :(得分:2)
不错,但绝对不理想。我对此的看法是,您不应该在构造函数中为对象创建上下文,其中上下文对象将创建对象。上下文应该是您正在创建的对象外部的东西。
但是,使用上下文对象创建“无限”循环排序没有问题。 ASP.NET MVC ControllerContext在方式上做了非常相似的事情;你通常不会这样称呼它:
ControllerContext.Controller.ControllerContext.Controller // etc
答案 1 :(得分:2)
如果没有领域知识,在设计循环依赖时很难知道从哪里开始,所以我只是概括。
当您没有表达层次关系时,循环引用很糟糕,在这种关系中,返回父级是必需的或有用的。这是因为它促进了循环中类型之间的强耦合,很难正确构造/删除,并且在遍历时容易出错。
但是,如果您具有层次关系,并且遍历层次结构是必需/有用的,那么拥有循环引用是完全合理的。
您可能想要避免的一件事是尝试一次调用太深的属性。这会增加您的耦合,并且很容易导致错误,例如您发现的堆栈溢出异常。
// Reaches too far. Makes this depend on the interface of SomeProperty
this.Context.SomeProperty.DoSomething();
// ...
// Not reaching too far. Only depends on Context.
// This might forward to SomeProperty.DoSomething()
this.Context.DoSomething();
根据您的修复方式,这也可能有助于您打破堆栈溢出。
答案 2 :(得分:0)
循环关系在非托管环境中很糟糕,因为您需要弱引用来打破循环关系(否则您将不可避免地泄漏循环中涉及的对象)。我无法想出给他们贴上标签的具体理由"糟糕"但是,在托管环境中。
事实上,有很多充分理由要求循环引用。例如,在UI树中,父UI元素知道他们的孩子,孩子也知道他们的父母。
答案 3 :(得分:-1)
通常,周期性参考是不良设计的气味。但是有几种情况,循环引用的方式很好(在Winforms中:Control.Controls与Control.Parent)。
如果您有周期性参考,则应检查是否有正确的值。如果你有父子关系,并且从父母那里删除了孩子,那么孩子不应该引用父母,父母不应该引用孩子。