Возникли проблемы с пониманием ninject (или просто контейнера IOC в целом) по сравнению с заводским DI? - PullRequest
6 голосов
/ 26 января 2012

Ладно, недавно я читал ниндзя, но у меня возникли проблемы с пониманием того, что делает лучше, чем то, что на вики-странице они называют "DI" для бедняков. Печально то, что я просмотрел все их страницы в вики и до сих пор не понимаю = (.

Обычно я оборачиваю свои классы обслуживания в фабричный шаблон, который обрабатывает DI следующим образом:

public static class SomeTypeServiceFactory
{
    public static SomeTypeService GetService()
    {
        SomeTypeRepository someTypeRepository = new SomeTypeRepository();
        return = new SomeTypeService(someTypeRepository);

    }

}

Что мне кажется очень похожим на модули:

public class WarriorModule : NinjectModule {
    public override void Load() {
      Bind<IWeapon>().To<Sword>();
      Bind<Samurai>().ToSelf().InSingletonScope();
    }
}

Где каждый класс будет иметь свой связанный модуль, и вы привязываете его конструктор к конкретной реализации. Несмотря на то, что пустой код на 1 строку меньше, я просто не вижу преимущества, каждый раз, когда вы добавляете / удаляете конструкторы или изменяете реализацию конструктора интерфейса, вам придется менять модуль почти так же, как на фабрике. нет? Так что не вижу здесь преимущества.

Тогда я подумал, что смогу придумать общую фабрику, основанную на соглашениях, вот так:

 public static TServiceClass GetService<TServiceClass>()
        where TServiceClass : class
    {
        TServiceClass serviceClass = null;

        string repositoryName = typeof(TServiceClass).ToString().Replace("Service", "Repository");
        Type repositoryType = Type.GetType(repositoryName);

        if (repositoryType != null)
        {
            object repository = Activator.CreateInstance(repositoryType);
            serviceClass =  (TServiceClass)Activator.CreateInstance(typeof (TServiceClass), new[]{repository});
        }

        return serviceClass;
    }

Однако это дерьмово по двум причинам: 1) оно тесно зависит от соглашения об именах, 2) предполагается, что в хранилище никогда не будет никаких конструкторов (не так), и единственный конструктор службы будет соответствующим репозиторием (также не правда). Мне сказали «эй, это то место, где вы должны использовать контейнер IoC, здесь было бы здорово!» И поэтому мое исследование началось ... но я просто не вижу его и испытываю трудности с пониманием. ..

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

Прочтение этого вопроса и / или демонстрация того, как его можно использовать на моей общей фабрике, было бы очень полезно!

РЕДАКТИРОВАТЬ: Ответ

Так что благодаря приведенному ниже объяснению я был в состоянии полностью понять удивительность ninject, и моя общая фабрика выглядит так:

public static class EntityServiceFactory
{
    public static TServiceClass GetService<TServiceClass>()
        where TServiceClass : class
    {
        IKernel kernel = new StandardKernel();

        return kernel.Get<TServiceClass>();
    }
}

Довольно круто. Все обрабатывается автоматически, поскольку конкретные классы имеют неявное связывание.

Ответы [ 2 ]

7 голосов
/ 26 января 2012

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

Пример: если у вас есть сервис MyService : IMyService и класс A, для которого требуется IMyService, вы должны сообщить Ninject, как он должен это делать.Разрешите эти типы, как на вашем заводе.Здесь выгода минимальна.Но как только ваш проект растет и вы добавляете класс B, который также зависит от IMyService, вы просто должны указать Ninject, как разрешить B.Ninject уже знает, как получить IMyService.С другой стороны, на фабрике вы должны снова определить, как B получает IMyService.

, чтобы сделать еще один шаг вперед.Вы не должны определять привязки один за другим в большинстве случаев.Вместо этого используйте основанную на соглашении конфигурацию (Ninject.Extension.Conventions).При этом вы можете группировать классы (службы, репозитории, контроллеры, докладчики, представления и т. Д.) И настраивать их таким же образом.Например, сообщите Ninject, что все классы, оканчивающиеся на Service, должны быть синглетонами и опубликовать все их интерфейсы.Таким образом, у вас есть одна конфигурация, и при добавлении другой службы никаких изменений не требуется.

Также контейнеры IoC - это не просто фабрики.Есть намного большеНапример, управление жизненным циклом, перехват, ....

kernel.Bind(
    x => x.FromThisAssembly()
          .SelectAllClasses()
          .InNamespace("Services")
          .BindToAllInterfaces()
          .Configure(b => b.InSingletonScope()));
kernel.Bind(
    x => x.FromThisAssembly()
          .SelectAllClasses()
          .InNamespace("Repositories")
          .BindToAllInterfaces());
2 голосов
/ 26 января 2012

Чтобы быть полностью аналогичным, ваш заводской код должен выглядеть следующим образом:

public static class SomeTypeServiceFactory
{
    public static ISomeTypeService GetService()
    {
        SomeTypeRepository someTypeRepository = new SomeTypeRepository();

        // Somewhere in here I need to figure out if i'm in testing mode 
        // and i have to do this in a scope which is not in the setup of my
            // unit tests

        return new SomeTypeService(someTypeRepository);
    }

    private static ISomeTypeService GetServiceForTesting()
    {
        SomeTypeRepository someTypeRepository = new SomeTypeRepository();
        return new SomeTestingTypeService(someTypeRepository);
    }
}

И эквивалент в Ninject будет:

public class WarriorModule : NinjectModule {
    public override void Load() {
      Bind<ISomeTypeService>().To<SomeTypeService>();
    }
}

public class TestingWarriorModule : NinjectModule {
    public override void Load() {
      Bind<ISomeTypeService>().To<SomeTestingTypeService>();
    }
}

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...