Внедрение зависимостей с помощью Ninject, MVC 3 и использованием шаблона локатора сервиса

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

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

Тем не менее, во многих местах я использовал 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, невозможно использовать Dependency Injection для конструктора для этой конкретной реализации.

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

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

Мне показалось, что я, по крайней мере, достаточно хорошо понял концепцию разделения проблем, но, похоже, что-то в корне неверно в том, как я понимаю внедрение зависимостей и шаблон локатора служб - и я не знаю, что это такое.

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

  1. Когда это не приемлемо?
  2. Какой тип шаблона мне следует искать, когда я должен переосмыслить то, как я использую шаблон Service Locator?
  3. Моя реализация ModelBinder неверна? Если это так, что мне нужно научиться это исправить?
  4. В другом вопросе, аналогичном этому одному пользователю, Mark Seemann порекомендовал абстрактную фабрику - как это связано?

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

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

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

Я бы хотел, чтобы stackru.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>();
  }
}

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

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

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

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

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

Другие вопросы по тегам