ASP.NET MVC: Преобразование или инъекции? Вот в чем вопрос

в 12:50, , рубрики: asp.net mvc, Веб-разработка, Программирование

Классы должны иметь зависимости только на абстрактные, а не конкретные, классы. Чтобы решить эту задачу, вы можете выбрать между 2-х практик Service Locator и Dependency Injection. ASP.NET MVC использует “Dependency Resolvers", которые являются локаторами служб (Service Locators). При разработке приложений ASP.NET MVC вы сами должны решить что следует использовать преобразования (resolve) или инъекции (injects), так каковы плюсы и минусы?

Я понимаю что Dependency Injection имеет очень большую ценность в разрешении проблемы зависимости, но я не верю что она является единственно верной. Dependency Injection является лишь деталью реализации, что действительно имеет значение принцип, лежащий в ее основе — принцип инверсии (Dependency Inversion) зависимостей. Если вы хорошо знакомы с аббревиатурой SOLID, принцип инверсии зависимостей просто «D» в аббревиатуре. Кстати, солидные результаты от инициалов пять принципов проектирования, которые считаются необходимыми в объектно-ориентированной разработки программного обеспечения. Вот эти пять принципов:

  • Принцип единственной обязанности
  • Принцип открытости/закрытости
  • Принцип подстановки Барбары Лисков
  • Принцип разделения интерфейса
  • Принцип инверсии зависимостей

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

Внутренности принципа инверсии зависимостей

Принцип инверсии зависимостей, напротив, имеет довольно неясную формулировку, но может быть переведен в подробный наборе этапов реализации. Принцип инверсии зависимостей гласит, что классы не должны иметь зависимостей от конкретных классов, но только в абстракции. На более понятном языке, это означает, что вы должны использовать интерфейсы для абстрагирования всех критических зависимостей внутри класса. Если, например, ваш класс использует компонент логирования, то лучшее, что вы можете сделать, чтобы сделать это объявить ILogger интерфейс, а не класс Logger. Таким образом, вы можете изменить реализацию класса логирования в любое время (и сколько раз вы хотите), не нарушая вашего основного кода.
Смысл алгоритма реализации принципа инверсии зависимостей, заключается в следующем что вы передаете список зависимостей в необходимое место в коде. Давайте рассмотрим это на примере:

public class MyComponent
{
    public MyComponent()
    {
       :
    }
    public void DoSomeWork()
    {
        var logger = new Logger();
        :
    }
}

Очевидно, что MyComponent класса имеет зависимость от Logger класса. Если мы решим изменить его на ILogger, как мы можем получить ссылку на фактический класс, реализующий интерфейс?

public class MyComponent
{
    public MyComponent()
    {
       :
     }
     public void DoSomeWork()
     {
          ILogger logger = ...; // who’s going to provide this?
          :
     }
}

Для реализации принципа инверсии зависимостей, у вас есть выбор из двух основных моделей: Service Locator и Dependency Injection. Первый способ решает проблему внутри класса, вторая позволяет вывести зависимость из класса. Следующий листинг иллюстрирует, метод Service Locator:

public class MyComponent
{
    public MyComponent()
    {
        :
    }
    public void DoSomeWork()
    {
        ILogger logger = ServiceLocator.GetService();
        :
    }
}

У вас есть компонент разрешения зависимостей, которой обычно занимает типа (обычно интерфейс) и возвращает экземпляр конкретного типа, который реализует этот интерфейс. Согласование типа, который передается и конкретно-экземпляр типа скрыты в реализации компонента локатора. Эта модель известна как шаблон Service Locator.
Вот еще один подход:

public class MyComponent
{
    private ILogger _logger;
    public MyComponent(ILogger logger)
    {
        _logger = logger;
    }
    public void DoSomeWork()
    {
        // Use the logger component here
        _logger.Log();
        :
    }
}

В этом случае MyComponent класс получает ILogger компонент для использования с внешним миром. Ваше окружение будет заботиться о инициализации регистратора перед передачей его до MyComponent. Это сущность модели инъекции зависимостей.
В чем разница (если таковые имеются) между инъекции зависимостей(Dependency Injection) и Service Locator? Обе модели хороши в реализации принципа инверсии зависимостей. Модель Service Locator проще в использовании в существующий код, это делает общий дизайн слабее, не заставляя изменения в публичный интерфейс. По этой же причине, код, основанный на модели Service Locator хуже читать, чем эквивалентный код, основанный на Dependency Injection.
В Dependency Injection ясно видно какой будет тип до и после инъекции в класс (или метод). По этой причине, в результате код становится чище и более удобным для чтения. А как насчет ASP.NET MVC?

Dependency Injection в ASP.NET MVC

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

Provider Описание
Action Invoker // В конструкторе класса контроллера controller.ActionInvoker = new YourActionInvoker();
Controller factory // В global.asax, Application_Start
var factory = new YourControllerFactory(); ControllerBuilder.Current.SetControllerFactory(factory);
Dictionary values // В global.asax,
Application_Start var providerFactory = new YourValueProviderFactory(); ValueProviderFactories.Factories.Add(providerFactory);
Model binder // В global.asax, Application_Start ModelBinders.Binders.Add(typeof(YourType), new YourTypeBinder());
Model binder provider // В global.asax, Application_Start var provider = new YourModelBinderProvider(); ModelBinderProviders.BinderProviders.Add(provider);
Model metadata // В global.asax, Application_Start ModelMetadataProviders.Current = new YourModelMetadataProvider();
Model validator // В global.asax, Application_Start var validator = new YourModelValidatorProvider(); ModelValidatorProviders.Providers.Add(validator);
TempData // В конструкторе класса контроллера
controller.TempDataProvider = new YourTempDataProvider();
View engine // В global.asax, Application_Start ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(new YourViewEngine());

View engine // В global.asax, Application_Start ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(new YourViewEngine());
До ASP.NET MVC 3, не было стандартного способа регистрации пользовательских компонентов. Каждый из компонентов, перечисленных в Таблице 1 требует своего собственного API для интеграции в пользовательские приложения. Начиная с версии 3, ASP.NET MVC вводит новых (дополнительных) модели, основанные на зависимости арбитров. Для замены компонентов системы, вы можете идти по пути описанной в таблице 1 или зарегистрировать инъекцию (Dependency Injection) для данного типа. Среда выполнения ASP.NET будет обнаружить зависимости и ссылаться на неё, когда это будет необходимо.
Зависимость разрешения это просто услуга локатор интегрированных с ASP.NET MVC кода. Арбитры являются одним из способов, чтобы добавить реализацию принципа инверсии зависимостей в существующих (больших) кода. Для размера и сложности кода, использование зависимостей менее целесообразным, поскольку это потребовало бы изменения на различных уровнях, в государственном API. Это просто не вариант для основы, такие как ASP.NET MVC. Давайте узнаем более подробную информацию о реализации зависимость арбитров в ASP.NET MVC.

Определение вашего преобразователя зависимостей ( Dependency Resolver)

В ASP.NET MVC Dependency Resolver является объект, реализующий следующий интерфейс

{
    Object GetService(Type serviceType);
    IEnumerable<Object> GetServices(Type serviceType);
}

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

public class SampleDependencyResolver : IDependencyResolver
{
    public object GetService(Type serviceType)
    {
        if (serviceType == typeof(ISomeClass))
            return new SomeClass();
        :
    }
    public IEnumerable<object> GetServices(Type serviceType)
    {
        return Enumerable.Empty<Object>();
    }
}

Следующей код показывает, как преобразователь использует Unity (и его раздел в файое конфигурации) для разрешения зависимостей.

public class UnityDependencyResolver : IDependencyResolver
{
  private readonly IUnityContainer _container;

  public UnityDependencyResolver() : this(new UnityContainer().LoadConfiguration())
  {
  }

  public UnityDependencyResolver(IUnityContainer container)
  {
    _container = container;
  }

  public Object GetService(Type serviceType)
  {
    return _container.Resolve(serviceType);
  }

  public IEnumerable<Object> GetServices(Type serviceType)
  {
    return _container.ResolveAll(serviceType);
  }
}

Вы регистрируете свой разрешения со структурой ASP.NET MVC используя класс DependencyResolver и SetResolver метод

protected void Application_Start()
{
   // Prepare and configure the IoC container
   var container = new UnityContainer();
   :

   // Create and register the resolver
   var resolver = new UnityDependencyResolver(container);
   DependencyResolver.SetResolver(resolver);
}

Если вы используете рамки IoC из разрешения, то вам нужно найти, лучший способ предоставить ему список зарегистрированных типов. Если вы предпочитаете, чтобы передать эту информацию через свободный код, то вам необходимо полностью настроить IoC контейнер до создания имен. Если вы хотите настроить IoC с использованием web.config файла, по правилам Unity, то, можно использовать конструктор по умолчанию для разрешения который требуется что бы загрузить данные конфигурации. Замечу, однако, что вам может понадобиться изменить этот код, если вы нацелены различные рамки IoC.
В целом, зависимость разрешения является внутренним инструментом, который разработчики могут дополнительно использовать что бы развернуть свои собственные индивидуальные компоненты, а не компоненты системы. Красота преобразователя зависимости ограничивается использованием ASP.NET MVC. Преобразователи вызываются в известных местах для достижения понятных целий. Другими словами, если ASP.NET MVC не вызывает преобразование, прежде чем создавать, скажем например, кэш контроллер, то мы не к сожалению так много можем сделать, чтобы заменить встроенный кэш на свой собственный.

Использование преобразований в приложениях

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

public class SampleDependencyResolver : IDependencyResolver
{
    public object GetService(Type serviceType)
    {
        try
        {
            return serviceType == typeof(ModelMetadataProvider)
                ? new ExtendedAnnotationsMetadataProvider()
                : Activator.CreateInstance(serviceType);
            }
            catch
            {
            return null;
        }
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        return Enumerable.Empty<object>();
    }
}

GetService метод получает тип, и проверяет его со списком известных типов. Для некоторых известных типов интерфейсов, он может просто вернуть вручную созданный экземпляр известного типа. Для других типов может вернуть ничего, это означает, что арбитр не в состоянии работать с этотим типом.
Преобразователь зависимость в ASP.NET MVC никак не может преобразовывать один и тот же типа по-разному в течение жизненного цикла приложения. Хотя это может быть существенным ограничением в реализации общих службе локатора, но не большая проблема в конкретном контексте ASP.NET MVC. преобразование зависимостей является внутренней особенностью ASP.NET MVC, и только ASP.NET MVC решает, когда и как вызвать и использовать зарегистрированный преобразователи. В конечном итоге, ваш преобразователь будет вызываться только в ограниченном числе случаев, а интерфейс станет более чем понятен.

Так что же использовать преобразования или иньекции ?

Если вы посмотрите на этот вопрос с точки зрения эффективности дизайна, то Dependency Injection является предпочтительным, потому что это приводит дизайну к чистой и кристально-четкому распределению обязанностей. Чтобы использовать Dependency Injection, возможно, придется принять на себя смелость изменить ваш интерфейс API. Это может быть не приемлемым в зависимости от контекста, это было бы не приемлемо, например, при переходе от ASP.NET MVC 2 в ASP.NET MVC 3. По этой причине, Microsoft остановила свой выбор на использовании преобразователей зависимости, причудливое название для классических компонент Service Locator. Более реалистичный пример, служба локатора является единственным вариантом, когда вам необходимо сделать рефакторинг в большом существующем приложении

p.s. Это мой первый перевод спасибо за понимание.

Автор: a3code

Поделиться

* - обязательные к заполнению поля