Инъекция зависимостей с помощью Ninject, MVC 3 и с использованием шаблона Locator

Что-то, что меня прослушивало с тех пор, как я прочитал ответ на другой вопрос о стеке (точное ускользает от меня сейчас), где пользователь сказал что-то вроде ". Если вы вызываете Service Locator, вы это делаете неправильно."

Это был человек с высокой репутацией (в сто тысяч, я думаю), поэтому я склонен думать, что этот человек может знать, о чем они говорят. Я использую DI для своих проектов, так как я впервые начал об этом узнавать и насколько хорошо это относится к Unit Testing, а что нет. Мне сейчас что-то довольно комфортно, и я думаю, что знаю, что делаю.

Тем не менее, есть много мест, где я использовал Service Locator для разрешения зависимостей в моем проекте. Как только основной пример приходит из моих реализаций ModelBinder.

Пример типичного связующего типа.

public class FileModelBinder : IModelBinder {
    public object BindModel(ControllerContext controllerContext,
                            ModelBindingContext bindingContext) {
        ValueProviderResult value = bindingContext.ValueProvider.GetValue("id");

        IDataContext db = Services.Current.GetService<IDataContext>();
        return db.Files.SingleOrDefault(i => i.Id == id.AttemptedValue);
    }
}

не реальная реализация - просто быстрый пример

Так как для реализации ModelBinder требуется новый экземпляр, когда запрашивается Binder first, невозможно использовать Dependency Injection для конструктора для этой конкретной реализации.

Это так во многих моих классах. Другим примером является процесс истечения срока действия кеша, который запускает метод, когда объект кэша истекает на моем веб-сайте. Я запускаю множество запросов к базе данных, а что нет. Там тоже я использую Locator для получения требуемой зависимости.

Еще одна проблема, с которой я недавно столкнулся (о том, что я задал здесь вопрос), заключалась в том, что все мои контроллеры потребовали экземпляр IDataContext, для которого я использовал DI, но для одного метода действий требуется другой экземпляр IDataContext. К счастью, Ninject пришел на помощь с именованной зависимостью. Тем не менее, это было похоже на kludge, а не на реальное решение.

Я думал, что, по крайней мере, я поняла концепцию Разделения проблем достаточно хорошо, но, похоже, что-то принципиально неправильно с тем, как я понимаю Injection Dependency и Pattern Locator Service, и я не знаю, что это такое.

То, как я это понимаю в данный момент, - и это может быть ошибочным - это то, что, по крайней мере, в MVC, ControllerFactory ищет конструктор для контроллера и вызывает сам локатор службы, чтобы получить необходимые зависимости, а затем передает их но я могу понять, что не все классы и не имеют Factory для их создания. Таким образом, мне кажется, что некоторые шаблоны Locator являются приемлемыми... но...

  • Когда это неприемлемо?
  • Какую модель следует искать, когда я должен переосмыслить, как я использую шаблон Locator службы?
  • Является ли моя версия ModelBinder неправильной? Если да, то что мне нужно, чтобы научиться его исправлять?
  • В другом вопросе в строках этого одного пользователя Марк Семанн рекомендовал абстрактный Factory - как это соотносится?

Я предполагаю, что это - я не могу думать о каком-либо другом вопросе, чтобы помочь моему пониманию, но любая дополнительная информация очень ценится.

Я понимаю, что DI не может быть ответом на все, и я, возможно, перейду за борт в том, как я его реализую, однако, похоже, он работает так, как я ожидаю, с Unit Testing, а что нет.

Я не ищу код для исправления моей реализации примера. Я ищу, чтобы узнать, ищет объяснение, чтобы исправить мое ошибочное понимание.

Я хочу, чтобы у stackoverflow.com была возможность сохранить проекты вопросов. Я также надеюсь, что тот, кто отвечает на этот вопрос, получает достаточную репутацию для ответа на этот вопрос, поскольку я думаю, что я прошу многого. Спасибо, заранее.

Ответы

Ответ 1

Рассмотрим следующее:

public class MyClass
{
  IMyInterface _myInterface;
  IMyOtherInterface _myOtherInterface;

  public MyClass(IMyInterface myInterface, IMyOtherInterface myOtherInterface)
  {
    // Foo

    _myInterface = myInterface;
    _myOtherInterface = myOtherInterface;
  }
}

С этой конструкцией я могу выразить требования к зависимостям для моего типа. Сам тип не несет ответственности за знание того, как создавать экземпляры любой из зависимостей, они даются ему (вводятся) каким-либо разрешительным механизмом (обычно это контейнер IoC). Принимая во внимание:

public class MyClass
{
  IMyInterface _myInterface;
  IMyOtherInterface _myOtherInterface;

  public MyClass()
  {
    // Bar

    _myInterface = ServiceLocator.Resolve<IMyInterface>();
    _myOtherInterface = ServiceLocator.Resolve<IMyOtherInterface>();
  }
}

Наш класс теперь зависит от создания specfic-экземпляров, но через делегирование на локатор сервисов. В этом смысле расположение службы можно рассматривать как анти-шаблон, потому что вы не подвергаете зависимостям, но вы разрешаете проблемы, которые могут быть пойманы путем компиляции, чтобы взорваться во время выполнения. (Хорошее чтение здесь). Вы скрываете сложности.

Выбор между тем или другим действительно зависит от того, что ваше здание на вершине и предоставляемых им услуг. Обычно, если вы создаете приложение с нуля, я бы выбрал DI все время. Он улучшает ремонтопригодность, повышает модульность и упрощает тестирование типов. Но, взяв ASP.NET MVC3 в качестве примера, вы можете легко реализовать SL, как его запекли в дизайне.

Вы всегда можете использовать композитный дизайн, где вы можете использовать IoC/DI с SL, так же, как с помощью Локатора общих служб. Компоненты могут быть подключены через DI, но через SL. Вы даже можете добавить композицию в микс и использовать что-то вроде Managed Extensibility Framework (которая сама поддерживает DI, но также может быть подключена к другим контейнерам IoC или локаторам сервисов). Это большой выбор дизайна, чтобы сделать, как правило, мои рекомендации касались IoC/DI, где это возможно.

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

С абстрактным шаблоном factory factory специализируется на создании определенных типов. Вы не регистрируете типы для разрешения, вы, по существу, регистрируете абстрактный factory и строит любые типы, которые могут потребоваться. С помощью Service Locator он предназначен для поиска служб и возврата этих экземпляров. Похоже на конвенциональную точку зрения, но очень по-разному в поведении.