Автор: Sergey Teplyakov

Сегодня мы переходим к самому простому паттерну передачи зависимостей передаче через аргументы метода, Method Injection.

Существует две разновидности паттерна под названием Method Injection. В некоторых случаях под этим паттерном понимается установка зависимостей объекта с помощью вызова метода:

public interface IDependency {} public class CustomService {
private IDependency _dependency,
public void SetDependency(IDependency dependency)
{
_dependency = dependency,
} }

По сути, этот паттерн аналогичен рассмотренному ранее паттерну Property Injection с закрытом геттером, со всеми преимуществами и недостатками. Он применяется в языках без встроенной поддержки свойств, а также может успешно применяться в языке C#, если вам это больше нравится.

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

Описание

Суть паттерна Method Injection заключается в передаче зависимости определенному методу, необходимой для его успешной работы.

Назначение

Предоставить классу сервиса дополнительную информацию для выполнения определенной задачи.

Применимость

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

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

1. Метод является статическим и другие варианты не подходят.

public interface ICurrencyRate
{
int GetCurrencyRate(string currency),
}
// PaymentService
public static Money CalculatePayment(ICurrencyRate
currencyRate)
{
return new Money(),
}

В этом же контексте используется IFormatProvider в методе double.Parse и других аналогичных методах. Иногда этот подход применим и для бизнес объектов и может использоваться, например, для статической фабрики или других подобных целей.

2. Зависимость может изменяться от операции к операции.

Существует вариант паттерна Стратегия, при котором эта стратегия не может быть передана в аргументах конструктора, поскольку она требуется лишь одному методу и может изменяться от вызова к вызову. Классическим примером такой стратегии может служить стратегия сортировки, передаваемая методу List<,T>,.Sort().

Этот же подход может применяться и тогда, когда некоторая стратегия доступна в месте вызова операции, а не в месте создания объекта.

// Задает 'стратегию' форматирования отчета public interface IReportFormatter {
string GetFormatString(), } // ReportService public string CreateReport(IReportFormatter reportFormatter) {
return default(string), }

3. Передача локального контекста для выполнения операции.

Ряд паттернов проектирования, таких как Команда, Состояние и некоторые другие могут использовать дополнительный внешний контекст для выполнения операции. Этот же подход интенсивно используется в многопоточном программировании, когда в поток (или таймер) передается дополнительный контекст, известный вызывающему коду.

public interface ICommandContext {
int ProcessorCount { get, } } // CustomCommand public void Execute(ICommandContext context) {}
Известные применения

В составе .NET Framework этот подход используется достаточно интенсивно, как в контексте локальных стратегий, так и в контексте передачи контекста исполнения.

Локальные стратегии

IFormatProvider provider = new NumberFormatInfo { NumberDecimalSeparator = ',' }, // Задаем 'стратегию' разбора double var value = double.Parse('1,1', provider), IComparer<,int>, comparer = Comparer<,int>,.Default, var list = new List<,int>, {3, 4, 1}, // Передаем 'стратегию' сортировки list.Sort(comparer), var task = Task.Run(() =>, { }), TaskScheduler taskScheduler = TaskScheduler.Current, // Задаем 'стратегию' запуска 'продолжения' задачи task.ContinueWith(t =>, { },
taskScheduler),

Команды в WPF

ICommand command = new RelayCommand(), // В реальности сюда могут приходить данные, необходимые // для выполенния команды command.Execute('value'),

Дополнительный контекст в многопоточности

Все привыкли, что самый простой способ добраться до внешнего контекста заключается в захвате внешних переменных. Тем не менее, есть и олдскульный подход, который заключается в явном протаскивании этого контекста через метод:// Использование контекста для передачи данных в другой поток. var context = new CustomViewModel(), var thread = new Thread(o =>, {
var localContext = (CustomViewModel) o, }), thread.Start(context),

Ограничения

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

Кроме того, к этому паттерну нужно отнестись с осторожностью, если он применяется потому, что некоторая зависимость нужна лишь одному методу в классе и нам лень из-за этого протаскивать ее через конструктор. Иногда этот подход оправдан, но он также может означать низкую внутреннюю связность (low cohesion) класса и нарушение Принципа Единой Обязанности: точно ли все нормально, что некоторая зависимость нужна лишь одной операции и не нужна другим методам? Может здесь скрыто два класса?

В отличие от Constructor Injection и Property Injection данный паттерн носит более локальный характер и не является типовым паттерном управления зависимостями в приложении. Тем не менее, если под зависимостями понимать локальный контекст или локальную стратегию, то этот паттерн вполне применим на практике.

Заключение

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

Помогла статья? Оцените её!
0 из 5. Общее количество голосов - 0
 

You have no rights to post comments

Дмитрий Крикунов

Публикую статьи, обучающие курсы и новости по программированию: алгоритмам, языкам (С++, Java), параллельному программированию, паттернам и библиотекам (Qt, boost).