Проблемы, возникающие при попытке применить хорошую практику внедрения зависимостей - PullRequest
1 голос
/ 26 сентября 2011

Я уже давно использую IoC (в основном Unity) и Dependency Injection в .NET, и мне действительно нравится этот шаблон как способ поощрения создания классов программного обеспечения со слабой связью, который должен быть легче изолировать для тестирования..

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

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

  1. Классы со слишком большим количеством зависимостей.(«Любой класс, имеющий более 3 зависимостей, должен быть допрошен за нарушение SRP»).Я знаю, что этот вопрос часто встречается в вопросах внедрения зависимостей, но после прочтения у меня все еще нет ни одного момента, когда Эврика решает мои проблемы:

    • a) В большом приложении я неизменноЯ обнаружил, что мне нужно 3 зависимости только для доступа к инфраструктуре (примеры - ведение журнала, конфигурация, постоянство), прежде чем я доберусь до конкретных зависимостей, необходимых для того, чтобы класс выполнил свою (надеюсь единоличную) работу.Мне известен подход, который позволил бы реорганизовать и объединить такие группы зависимостей в один, но я часто нахожу, что это становится просто фасадом для нескольких других сервисов, а не несет какой-либо собственной ответственности.Могут ли определенные инфраструктурные зависимости игнорироваться в контексте этого правила, если считается, что класс все еще несет единственную ответственность?

    • b) Рефакторинг может добавить к этой проблеме.Рассмотрим довольно распространенную задачу - разбить класс, который стал немного большим, - вы перемещаете одну область функциональности в новый класс, и первый класс становится зависимым от него.Предполагая, что первому классу все еще нужны все зависимости, которые у него были раньше, теперь у него есть одна дополнительная зависимость.В этом случае я, вероятно, не против того, чтобы эта зависимость была более тесно связана, но все же лучше, чтобы контейнер предоставил ее (в отличие от использования new ... ()), что он может сделать даже без новой зависимости, имеющейего собственный интерфейс.

    • c) В одном конкретном примере у меня есть класс, отвечающий за запуск различных функций через систему каждые несколько минут.Поскольку все функции по праву принадлежат разным областям, этот класс заканчивается множеством зависимостей, чтобы иметь возможность выполнять каждую функцию.Я предполагаю, что в этом случае следует рассмотреть другие подходы, возможно, связанные с событиями, но до сих пор я не пытался сделать это, потому что я хочу координировать порядок выполнения задач, а в некоторых случаях применять логику, включающую результаты

  2. Как только я использую IoC в приложении, кажется, что почти каждый созданный мной класс, который используется другим классом, заканчивается регистрацией и/ или впрыскивается контейнером.Это ожидаемый результат или некоторые классы не имеют ничего общего с IoC?Альтернатива простого добавления чего-то нового в коде выглядит просто как запах кода, так как он тесно связан.Это тоже относится к пункту 1b выше.

  3. Я выполнил инициализацию всех контейнеров при запуске приложения, зарегистрировав типы для каждого интерфейса в системе.Некоторые представляют собой целенаправленные жизненные циклы одного экземпляра, где другие могут быть новыми экземплярами каждый раз, когда они разрешаются.Однако, поскольку последние являются зависимостями первого, на практике они тоже становятся единым экземпляром, поскольку они разрешаются только один раз - во время создания одного экземпляра.Во многих случаях это не имеет значения, но в некоторых случаях я действительно хочу разные экземпляры каждый раз, когда я выполняю операцию, поэтому вместо того, чтобы использовать встроенную функциональность контейнера, я вынужден либовместо этого - заводская зависимость, чтобы я мог принудить это поведение или ii) передать контейнер, чтобы я мог разрешать каждый раз.Оба эти подхода осуждаются в руководстве Николы, но я вижу, что я) меньшее из двух зол, и я использую его в некоторых случаях.

Ответы [ 2 ]

3 голосов
/ 26 сентября 2011

В большом приложении я всегда нахожу, что мне нужно 3 зависимости только для доступа к инфраструктуре (примеры - ведение журнала, конфигурация, постоянство)

imho инфраструктура не является зависимостями. У меня нет проблем с использованием сервис-локатора для получения регистратора (private ILogger _logger = LogManager.GetLogger()).

Однако, на мой взгляд, настойчивость не является инфраструктурой. Это зависимость. Разбейте свой класс на более мелкие части.

Рефакторинг может добавить к этой проблеме.

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

Создавайте интерфейсы в отдельном проекте (разделенный шаблон интерфейса) вместо добавления зависимостей к классам.

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

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

Как только я использую IoC в приложении, создается впечатление, что почти каждый созданный мной класс, который используется другим классом, в конечном итоге регистрируется и / или внедряется контейнером. *

Я регистрирую все, кроме бизнес-объектов, DTO и т. Д. В моем контейнере.

Я выполнил инициализацию всех контейнеров при запуске приложения, зарегистрировав типы для каждого интерфейса в системе. Некоторые представляют собой целенаправленные жизненные циклы одного экземпляра, где другие могут быть новыми экземплярами каждый раз, когда они разрешаются. Однако, поскольку последние являются зависимостями первого, на практике они тоже становятся единым экземпляром, поскольку они разрешаются только один раз - во время создания одного экземпляра.

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

Возможно, вы захотите прочитать мои рекомендации .

1 голос
/ 26 сентября 2011

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

Вместо внедрения фабрики вы можете решить эту проблему, внедрив прокси. Вот пример:

public interface ITransientDependency
{
    void SomeAction();
}

public class Implementation : ITransientDependency
{
    public SomeAction() { ... }
}

Используя это определение, вы можете определить прокси-класс в Root Composition на основе ITransientDependency:

public class TransientDependencyProxy<T> : ITransientDependency
    where T : ITransientDependency
{
    private readonly UnityContainer container;

    public TransientDependencyProxy(UnityContainer container)
    {
        this.container = container;
    }

    public SomeAction()
    {
        this.container.Resolve<T>().SomeAction();
    }
}

Теперь вы можете зарегистрировать TransientDependencyProxy<T> как синглтон:

container.RegisterType<ITransientDependency,
    TransientDependencyProxy<Implementation>>(
        new ContainerControlledLifetimeManager());

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

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

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

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