Dependency Injection pattern: три способа реализации в Silverlight

Просто о NET | создано: 23.04.2011 | опубликовано: 23.04.2011 | обновлено: 13.01.2024 | просмотров: 14499 | всего комментариев: 3

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

Паттерн Dependency Injection

Dependency Injection (инъекция зависимостей) – это паттерн программирования, который является одной из разновидностей шаблона “Обращения зависимостей” (Inversion of control или IoC, где изменение порядка связи является путём получения необходимой зависимости). Паттерн Dependency Injection помогает произвести разделение модулей и классов друг от друга, повышая тем самым, масштабируемость (Extensibility) приложения.

Как можно использовать Dependency Injection

Сначала пример, который содержит два класса, один – является оберткой на ORM и реализует все методы необходимые для работы с базой, второй – является ViewModel, который будет использовать представление.

/// <summary>
/// Реализация доступа к DAL.
/// Например, LinqToSql или Entity Framework
/// </summary>
public class DataServiceModule
{
   // этот класс имеет методы по работе с базой данных
}

/// <summary>
/// Класс ViewModel, который использует DAL.
/// Он будет использоваться в представлении (View).
/// </summary>
public class DataServiceViewModel
{
   DataServiceModule module = new DataServiceModule();
}

Строгая зависимость одного класса от другого очевидна, потому что один класс использует экземпляр другого. Имея в проекте не одну, а сотни, или даже тысячи, таких зависимостей накладывает на возможность масштабируемости большой жирный вопрос. А иногда вплоть до большого и жирного креста черного цвета. Для того чтобы “развязать” зависимости и следует использовать Dependency Injection.

Три способа реализации Dependency Injection

Можно выделить три способа реализации Dependency Injection:

  1. Вливание через конструктор (Constructor Injection)
  2. Вливание через свойство (Setter Injection)
  3. Вливание через метод инициализации (Interface-based Injection)

Давайте рассмотрим все три способа по порядку.

Пример реализации Constructor Injection

Реализация проста. Нужно добавить интерфейс и использовать его для вливания зависимостей в конструктор:

  /// <summary>
  /// Интерфейс
  /// </summary>
  public interface IDataService
  {

  }

  /// <summary>
  /// Реализация доступа к DAL.
  /// Например, LinqToSql или Entity Framework
  /// </summary>
  public class DataServiceModule : IDataService
  {
      // этот класс имеет методы по работе с базой данных
  }

  /// <summary>
  /// Класс ViewModel, который использует DAL.
  /// Он будет использоваться в представлении (View).
  /// </summary>
  public class DataServiceViewModel
  {
      private IDataService dataService;
      public DataServiceViewModel(IDataService service)
      {
          this.dataService = service;
      }
  }

Как видно из примера, теперь сам класс DataServiceModule уже никак не связан с классом DataServiceViewModel. Любой класс, реализующий интерфейс IDataService может для вливания в конструктор.

Пример реализации Setter Injection

Setter Injection для вливания зависимостей использует свойства, которые позволяет создавать и использовать ресурсы как можно позже. Например, чтобы “подключить” по вот такому принципу:

  <UserControl
      x:Class="SilverlightApplication1.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d"
      d:DesignHeight="300"
      d:DesignWidth="400"
      xmlns:vm="clr-namespace:SilverlightApplication1">
      <UserControl.DataContext>
          <vm:MainPageViewModel />
      </UserControl.DataContext>
      <Grid x:Name="LayoutRoot" Background="White"></Grid>
  </UserControl>

требуется конструктор без параметров (см. строку 12), потому что инициализировать входящие параметры в XAML невозможно. А теперь сам пример реализации Setter Injection:

   /// <summary>
   /// Интерфейс
   /// </summary>
   public interface IDataService
   {
   }
   /// <summary>
   /// Реализация доступа к DAL.
   /// Например, LinqToSql или Entity Framework
   /// </summary>
   public class DataServiceModule : IDataService
   {
       // этот класс имеет методы по работе с базой данных
   }
   /// <summary>
   /// Класс ViewModel, который использует DAL.
   /// Он будет использоваться в представлении (View).
   /// </summary>
   public class DataServiceViewModel
   {
       private IDataService dataService;
       public IDataService Service
       {
           get { return this.dataService; }
           set { this.dataService = value; }
       }
   }

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

Пример реализации Interface-based Injection

Вы, наверное, обратили внимание, что я не очень корректно перевел название третьего метода на русский язык. Я сделал это преднамеренно, чтобы максимально просто было понять принцип действия техники вливания. Вот пример:

/// <summary>
/// Интерфейс
/// </summary>
public interface IDataService
{

}

/// <summary>
/// Реализация доступа к DAL.
/// Например, LinqToSql или Entity Framework
/// </summary>
public class DataServiceModule : IDataService
{
    // этот класс имеет методы по работе с базой данных
}

/// <summary>
/// Класс ViewModel, который использует DAL.
/// Он будет использоваться в представлении (View).
/// </summary>
public class DataServiceViewModel
{
    private IDataService dataService;

    /// <summary>
    /// Инициализация сервиса данных в модуле
    /// </summary>
    /// <param name="service">Экземпляр IDataService</param>
    public void InitializeService(IDataService service)
    {
        this.dataService = service;
    }
}

Обратите внимание на метод инициализации (см. строка 30). Использовать его можно примерно по такому принципу:

IDataService dataService = new DataServiceModule();
DataServiceViewModel dataServiceViewModel = new DataServiceViewModel();
dataServiceViewModel.InitializeService(dataService);

Конечно, именно потому что используется интерфейсная модель как базовое вливание зависимостей в класс, он и был назван Interface-based Injection. Но зато мой вариант перевода названия, дает возможность явно себе представить “что”, “где” и “как”. Не правда ли?

Осталось процитировать вышесказанное: “Применять тот или иной способ следует на основе личных предпочтений, а также в зависимости от требований конкретного проекта.”

Обновление (26.04.2011): Спасибо за комментарии, уважаемые друзья. В комментариях, как и в спорах, родилась истина. Наверное, одна из статей, приведенная мной в оправдание сбила меня с толку. Тем не менее, привожу теперь правильный вариант Interface-based Injection:

/// <summary>
/// Интерфейс
/// </summary>
public interface IDataService
{
    void UpdateData();
}

/// <summary>
/// Реализация доступа к DAL.
/// Например, LinqToSql или Entity Framework
/// </summary>
public class DataServiceModule : IDataService
{
    // этот класс имеет методы по работе с базой данных
    public void UpdateData()
    {
        // обновление базы
    }
}

/// <summary>
/// Инерфейс "вливателя"
/// </summary>
public interface IServiceInjector
{
    void InitializeService(IDataService service);
}

/// <summary>
/// Класс ViewModel, который использует DAL.
/// Он будет использоваться в представлении (View).
/// </summary>
public class DataServiceViewModel : IServiceInjector
{
    private IDataService dataService;

    public DataServiceViewModel() { }

    /// <summary>
    /// Реализация интерфейса "вливателя"
    /// </summary>
    /// <param name="service">сервис</param>
    public void InitializeService(IDataService service)
    {
        this.dataService = service;
    }

    private void UpdateData()
    {
        this.dataService.UpdateData();
    }
}

/// <summary>
/// Другой класс, которому нужна инъекция сервисом
/// </summary>
public class AnotherClassNeedService : IServiceInjector
{
    private IDataService dataService;

    /// <summary>
    /// Реализация интерфейса "вливателя"
    /// </summary>
    /// <param name="service">сервис</param>
    public void InitializeService(IDataService service)
    {
        this.dataService = service;
    }

    private void UpdateData()
    {
        this.dataService.UpdateData();
    }
}

Теперь всё соответствует правилу: "Type 1 or interface injection, in which the exported module provides an interface that its users must implement in order to get the dependencies at runtime.".

Спасибо, дорогие Друзья!
Как оказалось, мир не без добрых людей.

Уважаемые программисты, будьте внимательны!

P.S.: Когда писал дату обновления, понял, что сегодня очередная годовщина трагедии на чернобыльской АЭС. Прошло 25 лет и такое же случилось на станции в Фукусиме. Наблюдается уже некоторая тенденция, а значит человечество погибнет не от всемирного потопа вызванного всемирным потеплением...

Комментарии к статье (3)

Привет.
Хорошая статья, спасибо. Лично мне непонятно как получилось, что то что программисты используют с древних времен (ну, не с древних, с начала 1990-х, когда обьектное программирование "пошло в массы") вдруг приобрело какое-то таинственное и маловразумительное понятие: dependency injection.
Я давно избегаю вызов new в других классах в пользу интерфейсов (ну где это не слишком обременительно), а сами экземпляры создаю в отдельном месте (теперь это называется Контейнер).
Короче, если бы Остап знал, что играет такие сложные партии, он бы удивился.
Спасибо.

Спасибо, хорошая статья

Большое спасибо за статью очень доходчиво никак не мог понять что и для чего.