具有多个项目和一个抽象工厂的控制台应用程序中的简单注入器

Simple Injector in Console Application with multiple projects and an abstract factory

长话短说;博士。我有一个循环依赖,不知道如何打破它。

Main.csproj: 有 Program.cs 手动实例化 DiService

var diService = new DiService(new Container());
diService.Register();

register 方法在 CurrentDomain 中搜索程序集并注册集合,其中存在针对给定接口的多个实现,或者在 1-1 的基础上注册具体情况。

然后它使用 Container 来实例化一个抽象工厂。

var diFactory = diService.Registry.GetInstance<IDiFactory>();

这里是工厂

public class DiFactory : IDiFactory
{
    private readonly Container registry;

    public DiFactory(Container registry)
    {
        this.registry = registry;
    }

    public T Get<T>()
    {
        var reqT = typeof(T);
        return (T) registry.GetInstance(reqT);
    }
}

解决方案中的项目依赖如下所示:

Main -> A -> B,E 
        B -> C,D,E
        C -> D,E
        D -> E

DiService 和 DiFactory 与其他服务一起存在于项目 B 中。这并不重要。我想如果他们在 Main 中,我也会遇到同样的问题。

项目 B 到 E 中的所有对象都有一个注入 DiFactory 的构造函数,因此它们可以在 运行 时间决定它们需要什么对象。但是C要使用它,就必须依赖B,这是一个循环依赖。

如果我将 DI 内容移动到新项目 F,那么所有项目都可以依赖它,但是工厂如何在不创建另一个循环依赖的情况下引用其他项目中的类型?

我遵循了 IRequestHandler 的文档,只是没有查字典。很可能我有设计缺陷,但我看不出它是什么。

这是 LinqPad 对象之间交互的示例 - 无法编译但看起来正确。

void Main()
{
    var diService = new Mine.Services.MyDiService();
    var diFactory = diService.Container.GetInstance<Mine.Services.IMyFactory>();
    var rand = new Random();
    var next = rand.Next(1, 100);
    var task = next % 2 == 0
        ? diFactory.Get<Mine.Tasks.EvenTask>()
        : (Mine.Tasks.IMyTask)diFactory.Get<Mine.Tasks.OddTask>();
    task.Perform();
}


namespace Mine.Common
{
    public class MyCommonObject { }
}

namespace Mine.Services
{
    public class FakeContainer
    {
        public T GetInstance<T>() { return default(T); }
    }
    public interface IMyOtherService { void DoSomethingElse(); }
    public class MyOtherService : IMyOtherService
    {
        public void DoSomethingElse()
        {
            throw new NotImplementedException();
        }
    }
    public class MyService
    {
        private readonly IMyFactory myFactory;
        public MyService(IMyFactory myFactory)
        {
            this.myFactory = myFactory;
        }
        public void MyServiceMethod()
        {
            var thing = myFactory.Get<Mine.Common.MyCommonObject>();
        }
    }

    public interface IMyFactory { T Get<T>(); }

    public class MyDiService
    {
        public FakeContainer Container;
    }
    public class MyFactory : IMyFactory
    {
        private FakeContainer Container;
        public MyFactory(FakeContainer container)
        {
            // obviously this is really a SImple Injector Container
            Container = container;
        }
        public T Get<T>()
        {
            return default(T);
        }
    }
}

namespace Mine.Kernel {
    public interface IMyMultiConcrete { void Do(); }
    public class MyConcreteBase : IMyMultiConcrete
    {
        protected readonly Mine.Services.IMyFactory MyFactory;
        public MyConcreteBase(Mine.Services.IMyFactory myFactory)
        {
            MyFactory = myFactory; 
        }
        public void Do()
        {
            MyFactory.Get<Mine.Common.MyCommonObject>();
        }
    }
    public class MyConcrete1 : MyConcreteBase
    {
        public MyConcrete1(Mine.Services.IMyFactory myFactory) : base(myFactory) {}
        public void Do()
        {
            MyFactory.Get<Mine.Common.MyCommonObject>();
        }
    }
}

namespace Mine.Tasks
{
    public interface IMyTask { void Perform(); }
    public class TaskBase : IMyTask
    {
        protected readonly Mine.Services.IMyOtherService MyOtherService;
        public TaskBase(Mine.Services.IMyFactory myFactory, Mine.Services.IMyOtherService myOtherService)
        {
            MyOtherService = myOtherService;
        }
        public void Perform()
        {
            MyOtherService.DoSomethingElse();
        }
    }

    public class OddTask : TaskBase
    {
        public OddTask(Mine.Services.IMyFactory myFactory, Mine.Services.IMyOtherService myOtherService)
        : base(myFactory, myOtherService) { }


    }


    public class EvenTask : TaskBase
    {
        public EvenTask(Mine.Services.IMyFactory myFactory, Mine.Services.IMyOtherService myOtherService)
        : base(myFactory, myOtherService) { }


    }
}

可能有更简单的方法,但我通常最终做的是有一个单独的程序集,其中包含我需要注入的所有接口。 主程序集(或者 B 在你的情况下可以这样做)执行接口到具体实现的绑定,每个人都可以引用接口程序集而不创建任何循环依赖关系。

工厂的接口也应该在该程序集中。

您所描述的IDiFactory抽象是不是抽象工厂设计模式的实现——它是服务定位器模式的实现。但是,服务定位器 is an anti-pattern 您应该停止使用它,因为它有很多缺点。

相反,类 不应该能够从服务定位器请求一组未绑定的依赖项,但 neither should they 通常能够使用抽象工厂请求一组固定的依赖项。相反,类 应该通过构造函数静态声明它们所需的依赖项。

此更改可能已经修复了循环依赖,因为您将首先删除 IDiFactory(导致循环的)。

DiService and DiFactory live in project B with the other services. Not that it matters.

将依赖项连接到哪里确实很重要。依赖关系应该连接到你的 Composition Root 并且这个 Composition Root 应该存在

As close as possible to the application’s entry point.

这很可能意味着您应该将其移至您的控制台应用程序。当您移动该代码时,只有启动程序集会依赖于使用过的 DI 容器。在这一点上,将 DI 容器隐藏在抽象后面变得无关紧要(正如你的 DiService 似乎暗示的那样)。不再需要隐藏,因为除了 Composition Root 之外,应用程序的其他部分都不知道如何构建依赖图。将 DI 容器隐藏在抽象之后,不会再增加可维护性。