Простой инжектор в консольном приложении с несколькими проектами и абстрактной фабрикой - PullRequest
0 голосов
/ 25 января 2019

TL; DR.У меня круговая зависимость, и я не знаю, как ее сломать.

Main.csproj: имеет Program.cs, который вручную создает DiService

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

Метод register ищет в CurrentDomain сборки и регистрирует коллекциигде для данного интерфейса существует несколько реализаций, или же они регистрируют конкреции 1-1.

Затем он использует контейнер для создания абстрактной фабрики.

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


    }
}

Ответы [ 2 ]

0 голосов
/ 26 января 2019

Эта IDiFactory абстракция, которую вы описываете, является , а не реализацией шаблона проектирования Abstract Factory - это реализация шаблона Service Locator. Сервисный локатор, однако, является анти-паттерном , и вы должны прекратить его использование, потому что его многочисленные недостатки.

Вместо этого классы не должны иметь возможность запрашивать несвязанный набор зависимостей из локатора служб, но также они не должны обычно иметь возможность запрашивать фиксированный набор зависимостей с помощью абстрактной фабрики. Вместо этого классы должны статически объявлять свои необходимые зависимости через конструктор.

Это изменение может уже исправить циклическую зависимость, поскольку вы сначала удалите IDiFactory (который вызывает цикл).

DiService и DiFactory живут в проекте B с другими службами. Не то чтобы это имело значение.

Неважно, куда вы подключаете свои зависимости. Зависимости должны быть подключены в вашем Композиционном корне , и этот Композиционный корень должен жить

Как можно ближе к точке входа приложения.

Скорее всего, это означает, что вам следует перенести это в консольное приложение. При перемещении этого кода только начальная сборка будет зависеть от используемого контейнера DI. В этот момент становится неуместным скрывать контейнер DI за абстракцией (как, по-видимому, подразумевается ваш DiService). Сокрытие больше не требуется, потому что никакие другие части приложения, кроме Composition Root, не будут знать, как строятся графы зависимостей. В этот момент скрытие контейнера DI за абстракцией больше не повышает удобство сопровождения.

0 голосов
/ 25 января 2019

Возможно, есть более простой способ, но обычно я получаю отдельную сборку, содержащую интерфейсы для всего, что мне нужно внедрить. Основная сборка (или B может сделать это в вашем случае) выполняет привязку интерфейсов к конкретным реализациям, и каждый может ссылаться на сборку интерфейсов без создания циклических зависимостей.

Интерфейс для фабрики также должен быть в этой сборке.

...