【问题标题】:Can you remove circular dependency of inner class factories?你能消除内部类工厂的循环依赖吗?
【发布时间】:2013-02-24 05:50:03
【问题描述】:

假设我有两个类,但它们只能通过工厂创建,为了强制执行,我将工厂创建为它们正在创建的对象的内部类,以便它们可以访问私有构造函数。在这种情况下,一个类需要创建另一个类的实例,而第二个类需要创建第一个类的实例:

public class A
{
    public class Factory
    {
        private readonly B.Factory bFactory;

        public Factory(B.Factory bFactory)
        {
            this.bFactory = bFactory;
        }

        public A Build()
        {
            return new A(this.bFactory);
        }
    }
    private A(B.Factory bFactory)
    {
    }
}

public class B
{
    public class Factory
    {
        private readonly A.Factory aFactory;

        public Factory(A.Factory aFactory)
        {
            this.aFactory = aFactory;
        }

        public B Build()
        {
            return new B(this.aFactory);
        }
    }
    private B(A.Factory aFactory)
    {
    }
}

通常我会通过创建另一个名为 ABFactory 的工厂来解决这个问题,该工厂可以同时创建 AB 实例,我会让 AB 都声明对 ABFactory 的依赖,但在这种情况ABFactory 无法访问AB 的私有构造函数。

假设语义是正确的(在这种情况下,A 返回B 的实例和B 返回另一个A 的实例是合乎逻辑的)那么最好的方法是什么解决这个问题?

更新 1

到目前为止,我的解决方案是创建一个名为 CircularDependencyResolver 的新类。两个工厂中的每一个都声明依赖于它而不是另一个工厂。每个人都在自己的构造函数中向该解析器注册自己(this)。当他们去构建各自的对象时,他们会调用解析器上的Resolve 方法,该方法会寻找其他注册的工厂,或者抛出异常。

问题当然是我们已经推迟解决 AB 的依赖关系,直到我们真正需要创建它,而不是在程序启动时,但它确实有效。

不过,我仍然对其他解决方案感兴趣。

【问题讨论】:

  • 虽然有些容器可能支持这个,但我认为这基本上是一个设计缺陷。我总是会尝试重新设计以一起消除循环依赖。循环会在很多层面(GC、编译、实例化等)上导致问题,这通常是您不想承担的技术债务
  • @PeterRitchie - 即使我没有循环依赖(所以我有一个ABFactory)我仍然不能让工厂调用两个私有构造函数。

标签: .net dependency-injection circular-dependency


【解决方案1】:

我的猜测:语义不可能是正确的。以哲学的方式,如果 A 只能在 B 的实例存在时创建(与这种情况下的常规构造相比,我在这里看不到工厂模式的相关性,因为您只是在代码方面移动问题,但不是语义明智的)和B只能在A存在时创建,那么没有办法,只要两者都不存在,就可以创建两者中的任何一个。

必须有一个,可以在没有另一个的情况下创建,除了 A 和 B 是同一事物的一部分,这里不是这种情况(因为 B 应该创建自己的 A 实例)。您需要使一个成为另一个的属性。

也许看看 OOP 中 aggregationcomposition 这两个概念的区别,以理清语义,

顺便说一句,您是否在依赖注入上下文中查看它并不重要,因为您无法以任何方式创建实例。

也许,我错过了一些东西……有趣的问题!

编辑:

好的,那这个呢:

public class A
{
    private int _privateInt;

    private B CreateB()
    {
        return B.Factory.Build();
    }

    public static class Factory
    {
        public static A Build()
        {
            return new A {_privateInt = 1};
        }
    }

    private A()
    {
    }
}

public class B
{
    public static class Factory
    {
        public static B Build()
        {
            return new B();
        }
    }

    private B()
    {
    }
}

但也许,我真的不明白这个问题?

编辑 2:

我无法真正描绘您的场景,但是为什么您需要将一个类的工厂传递给另一个类,尤其是在 DI 的上下文中?如果您想从另一个 (A) 中获取一个类(例如 B)的新实例,为什么不让容器为您创建该实例?

编辑 3:

如果您的容器支持为您的实例解析 Lazy 初始化程序,您可以将它们用作构造函数参数的类型:

public A(Lazy>B> b) {}

【讨论】:

  • 这样想:一个ViewPurchaseOrder控制器,它可以创建一个ViewCustomer控制器,它可以创建一个ViewAllPurchaseOrdersForCustomer控制器,它可以创建一个ViewPurchaseOrder控制器等等。在这种情况下,最后一个 ViewPurchaseOrder 控制器与第一个控制器是不同的实例。
  • 澄清一下,AB 都可以从其他地方创建,但它们都有创建另一个的能力。他们只要求先创建对方的工厂。这段代码最初是从非工厂方法重构而来的,其中A 只是直接实例化B(使用new),反之亦然。
  • 回复您的“编辑 2”:我没有将容器传递给我的对象。
  • @ScottWhitlock:您不一定需要,具体取决于容器。例如,在 Autofac 或 MEF 中,您可以通过在 B 的构造函数中传递 Lazy 来解决它,这就像一个“容器制造”工厂......
【解决方案2】:

首先,我当然同意@PeterRitchie 在他的评论中所说的,因为循环依赖是一种强大的设计气味,其次,为了打破循环依赖,我采用了我在 Marc Seemann 的书 Dependency Inject in .NET 中读到的建议,那就是:

尝试使用事件来解决周期问题。如果失败,请尝试 观察者。只有当你仍然不成功时,你才应该考虑 使用 PROPERTY INJECTION 打破循环。

如果这一切都失败了,我会使用面向对象语言著名的解决方案,即引入另一个依赖项或抽象来打破你实际所做的循环。

【讨论】:

    猜你喜欢
    • 2017-04-18
    • 1970-01-01
    • 2013-12-10
    • 1970-01-01
    • 2015-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-27
    相关资源
    最近更新 更多