Как поместить сервисы с сохранением состояния в корень композиции без ужасных побочных эффектов с помощью Dependency Injection? - PullRequest
1 голос
/ 20 февраля 2012

Рассмотрим случай, когда мы могли бы захотеть использовать службу, такую ​​как

class MyService {
    public double calculateSomethingVeryComplex();
}

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

class MyService {
    public double calculateSomethingVeryComplex()
    private double getA();
    private double getB();
    private double getC();
    ...
}

и, хотя метод main обычно может передавать свои объекты в getA(),getB(), ..., через аргументы метода мы обычно склонны определять их как атрибуты (поля) класса, чтобы выполнить работу:

class MyService {
    private double val1;
    private double val2;
    private double val3;

    public double calculateSomethingVeryComplex()
    private double getA();
    private double getB();
    private double getC();
    ...
}

Если не использовать внедрение зависимостей, каждый раз, когда мынам нужно вычислить что-то очень сложное, мы просто создаем новый MyService, вызываем его единственный метод, и все готово.

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

Я вижу 3 основныхварианты здесь:

  1. Создайте MyServiceFactory, который у меня есть, как зависимость от любого класса, который хочет использовать MyService.Мне не нравится этот подход, так как он может в некоторых приложениях стремительно увеличить количество классов и кажущуюся сложность системы.
  2. Инкапсулирует текущий класс MyService в другой, который несет ответственность за:создание экземпляра MyService, его запуск, а затем возврат вычисленного значения из него.Это кажется хорошим, так как открытый API класса такой же, как и исходный, и мы без проблем используем внедрение зависимостей.
  3. Пусть все будет так, как есть.Если мы попытаемся запустить 2 параллельных вычисления, у нас могут возникнуть проблемы с параллелизмом.Если мы попытаемся запустить один и тот же метод дважды один за другим, у нас могут возникнуть проблемы и с повторным использованием поля.

Второй пункт кажется очевидным победителем.Есть ли другая лучшая альтернатива пункту 2 или какой-либо из показанных точек?

Ответы [ 2 ]

3 голосов
/ 21 февраля 2012

Как описано, класс MyService страдает от запаха кода Temporary Field , как описано в Refactoring . Измените класс , и проблема исчезнет.

Если это невозможно, вам нужно убедиться, что каждый клиент получает частный экземпляр службы. Все DI-контейнеры поддерживают это через стиль жизни, который часто называют Transient .

В качестве третьей альтернативы вы также можете использовать это решение .

1 голос
/ 20 февраля 2012

Многие контейнеры для .NET поддерживают автоматическое создание фабрик.Если вы объявите зависимость конструктора

public MyConsumer(Func<MyService> factory) { ...}

и сообщите контейнеру, как разрешить MyService, он сгенерирует часть Func<> самостоятельно.

Таким образом, вам не нужносоздать любые классы, которые реализуют фабрику.Если вам нужно, чтобы параметры времени выполнения передавались в конструктор MyService, вы могли бы использовать что-то вроде Типизированные фабричные возможности Castle Windsor или , его порт Unity .

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


Обновление

Что делать, если каждому потребителю требуется более одного вызова MyService.method ()?

Сохраните фабрику в закрытом поле MyConsumer и используйте его всякий раз, когда вам нужен экземпляр MyService.Контейнер можно настроить так, чтобы он возвращал новый экземпляр MyService при каждом запросе.

public class MyConsumer
{
  private readonly Func<MyService> factory;
  public MyConsumer(Func<MySerivce> factory)
  {
    this.factory = factory;
  }
  public void NeedsMyServiceOnEveryCall(...)
  {
    var svc = factory();
    svc.Method();
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...