首页 > 解决方案 > 具有多个项目和抽象工厂的控制台应用程序中的简单注入器

问题描述

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

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

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

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

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

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) { }


    }
}

标签: c#dependency-injectionsimple-injector

解决方案


This IDiFactory abstraction you are describing is not an implementation of the Abstract Factory design pattern—it is an implementation of the Service Locator pattern. Service Locator, however, is an anti-pattern and you should stop using it because its numerous downsides.

Instead, classes should not be able to request an unbound set of dependencies from a Service Locator, but neither should they typically be able to request a fixed set of dependencies using an Abstract Factory. Instead, classes should statically declare their required dependencies through the constructor.

This change might already fix the circular dependency as you will remove the IDiFactory (that is causing the cycle) in the first place.

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

It does matter where you wire up your dependencies. Dependencies should be wired up in your Composition Root and this Composition Root should live

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

This most likely means that you should move this to your Console application. When you move that code, only the start-up assembly will take a dependency on the used DI Container. At that point, it becomes irrelevant to hide the DI Container behind an Abstraction (as your DiService seems to imply). Hiding is not needed anymore, because no other parts of the application except the Composition Root will have any knowledge about how dependency graphs are built. Hiding the DI Container behind an abstraction, at that point, doesn't increase maintainability any longer.


推荐阅读