Каков достойный способ внедрения зависимости в одноэлементный класс?

#design-patterns #language-agnostic #dependency-injection #singleton

#шаблоны проектирования #не зависит от языка #внедрение зависимости #singleton

Вопрос:

У меня есть синглтон, который зависит от другого класса, поэтому я хотел бы внедрить эту зависимость, чтобы сделать ее доступной для модульного тестирования. Поскольку нет способа использовать внедрение конструктора в одноэлементный класс, я предполагаю, что вместо этого я должен использовать ввод установщика, но мне это действительно не нравится, поскольку в какой-то момент кто-то (возможно, я сам) забудет вызвать установщик. Вы, конечно, могли бы внедрить зависимые объекты в метод getInstance синглтона, но это тоже довольно некрасиво. Есть ли какие-то лучшие способы решения этой проблемы (не используя такие инструменты, как контейнеры IoC)?

 public class Singleton {
    private ISomeDependency _dependency;
    private static final Singleton INSTANCE = new Singleton();
    private Singleton() {
    }
    public static Singleton getInstance() {
       return INSTANCE;
    }
    ...
}
  

Комментарии:

1. Хороший вопрос. Но я надеюсь, что вы примете ответы, которые уводят вас от выполнения того, что вы хотите выше.

Ответ №1:

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

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

Дизайн этого класса должен быть таким же, как у любого другого класса:

 public class SomeService : ISomeService 
{
    private ISomeDependency _dependency;

    public ISomeService(ISomeDependency dependency)
    {
        _dependency = dependency;
    }

    // ISomeService members here
}
  

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

Комментарии:

1. Спасибо за ответ. Но я не понимаю, как сделать абсолютно уверенным, что существует только один экземпляр класса, если не использовать одноэлементный шаблон? Поскольку конструктор должен быть общедоступным, любой может создать его экземпляр где угодно.

2. @Christian: В этом весь смысл создания экземпляров зависимостей только в корне композиции, то есть в верхней части приложения, как Main() , например. Конечно, вы можете создавать экземпляры зависимостей в других местах, но если у вас войдет в привычку никогда этого не делать (кроме как на фабриках и т.д.), Тогда мир станет более экологичным местом для жизни 🙂 Например, вы всегда ::memset() можете использовать произвольную память для мусора в любом месте приложения на C, но это не делает его правильным.

3. @Johann: да, я всего лишь ищу способ минимизировать риск неправильного использования решения и максимизировать вероятность его правильного использования. В реальном проекте всегда найдется кто-то, кто использует код не по назначению. В предлагаемом решении вы должны быть абсолютно уверены, что кто-то в какой-то момент создаст свой собственный экземпляр класса, который должен действовать как одноэлементный.

4. @Christian: Тогда это симптом более серьезных проблем. Если проверки кода работают и у команды есть согласованная общая методология проектирования, то неудивительно, что зависимости от служб не создаются сами по себе. То есть, если DI действительно используется в первую очередь.

5. @Christian: Я согласен с Йоханом в том, что касается использования обзоров кода и написания качественного кода. Если этого недостаточно, вы можете использовать такие инструменты, как NDepend или XDepend, чтобы проверить, работает ли ваше программное обеспечение по вашим правилам. В противном случае определите отдельный компонент (сборку / пакет), с которым связан только корневой каталог композиции. Таким образом, конкретной реализации просто не существует, только интерфейс. Лично я бы сделал это только тогда, когда все другие варианты не работают.

Ответ №2:

Вы можете использовать контейнер для внедрения зависимостей (например, Spring.Net, MS Unity и т.д.), Чтобы получить конкретный тип вашей зависимости.

 public class Singleton {
private ISomeDependency _dependency = Container.Resolve<ISomeDependency>();
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
   return INSTANCE;
}
...
}
  

Теперь у вас есть управление за пределами одноэлементного класса.

Комментарии:

1. Спасибо. Однако я хотел бы знать, каков предпочтительный способ решения этой проблемы без использования каких-либо контейнеров IoC. Я отредактирую свой вопрос.

2. Таким образом, у вас будет жесткая зависимость от вашего контейнера, что затрудняет написание модульных тестов. Вы всегда должны отдавать предпочтение использованию шаблона depedency inection, а не шаблона service locator.