Альтернатива проходящему контейнеру IOC

У меня был следующий базовый класс с несколькими зависимостями:

public abstract class ViewModel
{
    private readonly ILoggingService loggingService;

    public ViewModel(
        ILoggingService loggingService,
        ...)
    {
        this.loggingService = loggingService;
        ...
    }
}

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

public abstract class ViewModel
{
    private readonly IUnityContainer container;
    private ILoggingService loggingService;
    ...

    public ViewModel(IUnityContainer container)
    {
        this.container = container;
    }

    public ILoggingService LoggingService
    {
        get
        {
            if (this.loggingService == null)
            {
                this.loggingService = this.container.Resolve();
            }

            return this.loggingService;
        }
    }

    ...
}

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

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

2
«В моем производном классе я не хочу повторять все параметры в этом конструкторе базового класса» - либо использовать вложение свойств, либо автогенерировать конструктор, и делать с ним. (В любом случае, вы не можете действительно осмысленно разделить детские классы от своих родителей.)
добавлено автор millimoose, источник

9 ответы

Как вы заявляете, вы должны избегать прохождения контейнера. Это превращает его в «мешок холдинга», где вы больше не можете видеть, что такое ваши зависимости, и вы не можете легко увидеть, что находится в сумке.

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

Взгляните на список параметров и посмотрите, можете ли вы группировать параметры в более мелкие группы. Например, если ваш конструктор принимает IEmailSender , IEventLog и ILoggingService , возможно, вам действительно нужен INotificationService , который агрегирует эти три зависимости.

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

7
добавлено

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

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

Если у вас много зависимостей, это указывает на то, что класс нарушает шаблон одиночной ответственности.

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

5
добавлено
Не согласен с тобой! Думайте, что вам нужно передать более 5,6 параметров (на мой взгляд, слишком много) - ваш код становится нечитаемым. И вы знаете, вероятно, вы хотели бы добавить больше параметров в будущем ... Я думаю, что лучше всего сгруппировать эти параметры в отдельные классы.
добавлено автор Geka P, источник

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

Если у вас глобальная зависимость типа, которую вы, возможно, захотите использовать повсюду (журнал является прекрасным примером) ... просто используйте шаблон singleton ... (Заметьте, я также предполагаю, что контейнер также был создан с использованием шаблона singleton).

public static LoggingService
{
    private static ILoggingService _current;

    public static ILoggingService Current
    {
        get 
        {
            if(_current == null) { _current = Container.Current.Resolve(); }
            return _current;  
        }
    }
}

Затем используйте его, как ...

LoggingService.Current.Log(...);

Таким образом, вам не нужно вводить это во все.

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

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

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

1
добавлено

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

Wrong example - Foo worries about the dependencies of Bar as it needs to instanciate Bar.

class Foo {
    SomeDependency x;
    public Bar(SomeDependency x) {
        this.x = x;
    }
    public doSomething() {
        Bar bar = new Bar(x);
        bar.doSomething();
    }

}

class Bar {
    SomeDependency x;
    public Bar(SomeDependency x) {
        this.x = x;
    }
    public void doSomething() {
       //...
    }
}

Correct example - Foo does not care how Bar is created. Bar gets the dependencies directly from the container.

class Foo {
    SomeDependency x;
    Bar bar;

    public Bar(SomeDependency x, Bar bar) {
        this.x = x;
        this.bar = bar;
    }
    public doSomething() {
        bar.doSomething();
    }

}

class Bar {
    SomeDependency x;
    public Bar(SomeDependency x) {
        this.x = x;
    }
    public void doSomething() {
       //...
    }
}
1
добавлено
Что делать, если bar.doSomething (); нуждается в дополнительной зависимости AnotherDependency ? Поэтому bar.doSomething (AnotherDependency methodDependency); . Как вы справляетесь с этим делом?
добавлено автор tonix, источник
Я добавил небольшой пример, который иллюстрирует, что я имею в виду. Я настоятельно рекомендую смотреть Чистые кодовые разговоры - не ищите вещи! , которые объясняют эта концепция очень хорошо.
добавлено автор bikeshedder, источник
@Felix Добавление наследования не изменяет ответ и только затрудняет понимание моего ответа. У меня сложилось впечатление, что вопросник не следовал самой базовой идее IoC и DI: создайте и проведите свои объекты с помощью контейнера.
добавлено автор bikeshedder, источник
Но в этом примере Bar не получен из Foo ... так что это не относится к вопросу?
добавлено автор Felix, источник
Мое понимание вопроса состояло в том, что у вас есть Foo (Dep1 dep) , а затем у производного класса будет Bar (Dep1 dep, Dep2 dep2): base (dep) . Если бы у вас было много производных классов, вы могли бы представить, что писать Dep1 dep все время не нужно. Конечно, с помощью dep-inj вы, вероятно, не должны иметь так много производных классов в любом случае
добавлено автор Felix, источник

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

Марк Симен делает это очень красиво в своей книге с украшением. АОП является опрятной альтернативой, поскольку кто-то уже подчеркнул.

Ваш код должен стать:

public ViewModel(ILoggingService logger)
{
    loggingService= logger;
}

public ILoggingService LoggingService
{
    get
    {
        return this.loggingService;
    }
}
1
добавлено

AOP could be a solution if you want consistent behavior across a series of classes. Here's an example on logging with PostSharp : http://www.sharpcrafters.com/solutions/logging

Для ad-hoc-регистрации это может не соответствовать вашим потребностям.

1
добавлено

Я закончил создание интерфейса и класса ниже, используя шаблон фабрики, чтобы уменьшить количество параметров, добавленных в конструктор:

public interface IInfrastructureFactory
{
    ILoggingService LoggingService { get; }
   //... Other Common Services Omitted ...
}

public class InfrastructureFactory : IInfrastructureFactory
{
    private readonly ILoggingService loggingService;
   //... Other Common Services Omitted ...

    public InfrastructureFactory(
        ILoggingService loggingService,
       //... Other Common Services Omitted ...
        )
    {
        this.loggingService = loggingService;
       //... Other Common Services Omitted ...
    }

    public ILoggingService LoggingService
    {
        get { return this.loggingService; }
    }

   //... Other Common Services Omitted ...
}

В моем контейнере IOC я регистрирую IInfrastructureFactory один раз. В моей модели просмотра у меня только одна зависимость, и создание новой модели представления намного быстрее и проще.

public abstract class ViewModel
{
    private readonly IInfrastructureFactory infrastructureFactory;

    public ViewModel(IInfrastructureFactory infrastructureFactory)
    {
        this.infrastructureFactory = infrastructureFactory;
    }

    public ILoggingService LoggingService
    {
        get { return this.infrastructureFactory.LoggingService; }
    }

   //... Other Common Services Omitted ...
}
0
добавлено
Microsoft Stack Jobs
Microsoft Stack Jobs
1 788 участник(ов)

Work & freelance only Microsoft Stack. Feed https://t.me/Microsoftstackjobsfeed Чат про F#: @Fsharp_chat Чат про C#: @CSharpChat Чат про Xamarin: @xamarin_russia Чат общения:@dotnettalks

Microsoft Developer Community Chat
Microsoft Developer Community Chat
584 участник(ов)

Чат для разработчиков и системных администраторов Microsoft Developer Community. __________ Новостной канал: @msdevru __________ Баним за: оскорбления, мат, рекламу, флуд, флейм, спам, NSFW контент, а также большое количество оффтоп тем. @banofbot