Виндзор - вытащить временные объекты из контейнера

Как я могу вытащить объекты из контейнера, которые являются переходными по природе? Нужно ли регистрировать их в контейнере и вставлять в конструктор нужного класса? Впрыскивать все в конструктор не очень хорошо. Также только для одного класса я не хочу создавать TypedFactory и ввести фабрику в нуждающийся класс.

Еще одна мысль, которая пришла ко мне, была "новая" их по мере необходимости. Но я также делаю инъекцию Logger компонент (через свойство) во все мои классы. Поэтому, если я обновлю их, мне придется вручную создать экземпляр Logger в этих классах. Как я могу продолжать использовать контейнер для ВСЕХ моих классов?

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

//Install QueueMonitor as Singleton
Container.Register(Component.For<QueueMonitor>().LifestyleSingleton());
//Install DataProcessor as Trnsient
Container.Register(Component.For<DataProcessor>().LifestyleTransient());

Container.Register(Component.For<Data>().LifestyleScoped());

public class QueueMonitor
{
    private dataProcessor;

    public ILogger Logger { get; set; }

    public void OnDataReceived(Data data)
    {
        //pull the dataProcessor from factory    
        dataProcessor.ProcessData(data);
    }
}

public class DataProcessor
{
    public ILogger Logger { get; set; }

    public Record[] ProcessData(Data data)
    {
        //Data can have multiple Records
        //Loop through the data and create new set of Records
        //Is this the correct way to create new records?
        //How do I use container here and avoid "new" 
        Record record = new Record(/*using the data */);
        ...

        //return a list of Records    
    }
}


public class Record
{
    public ILogger Logger { get; set; }

    private _recordNumber;
    private _recordOwner;

    public string GetDescription()
    {
        Logger.LogDebug("log something");
        // return the custom description
    }
}

Вопросы:

  1. Как мне создать новый Record объект без использования "нового"?

  2. QueueMonitor является Singleton, в то время как Data является "Scoped". Как я могу ввести Data в OnDataReceived() метод?

1 ответ

Решение

Из образцов, которые вы даете, трудно быть очень конкретным, но в целом, когда вы вводите ILogger В большинстве случаев вы должны задать себе две вещи:

  1. Я слишком много вхожу?
  2. Я нарушаю твердые принципы?

1. Я слишком много вхожу

Вы слишком много регистрируетесь, когда у вас много такого кода:

try
{
   // some operations here.
}
catch (Exception ex)
{
    this.logger.Log(ex);
    throw;
}

Написание такого кода происходит из-за потери информации об ошибках. Дублирование таких видов блоков try-catch повсеместно не помогает. Хуже того, я часто вижу, как разработчики регистрируются и продолжают (они удаляют последние throw заявление). Это действительно плохо (и пахнет как старый VB ON ERROR RESUME NEXT), потому что в большинстве случаев у вас просто недостаточно информации, чтобы определить, безопасно ли продолжать. Часто в коде есть ошибка, которая приводила к сбою операции. Продолжение означает, что пользователь часто получает представление о том, что операция прошла успешно, а это не так. Задайте себе вопрос: что хуже: показать пользователю общее сообщение об ошибке, в котором говорится, что что-то пошло не так, или молча пропустить ошибку и позволить пользователю думать, что его запрос был успешно обработан? Подумайте, что будет чувствовать пользователь, если через две недели узнает, что его заказ так и не был отправлен. Вы, вероятно, потеряете клиента. Или, что еще хуже, регистрация MRSA у пациента молча завершается неудачей, из-за чего пациент не может быть помещен на карантин при кормлении грудью, что приводит к загрязнению других пациентов, что приводит к высокой стоимости или даже смерти

Большинство этих видов строк try-catch-log должны быть удалены, и вы должны просто позволить исключению пузыриться в стеке вызовов.

Вы не должны войти? Вы абсолютно должны! Но если вы можете, определите один блок try-catch в верхней части приложения. С ASP.NET вы можете реализовать Application_Error событие, зарегистрировать HttpModule или определите пользовательскую страницу ошибки, которая делает регистрацию. С Win Forms решение отличается, но концепция остается неизменной: определите одну единственную вершину, которая наиболее универсальна.

Тем не менее, иногда вы все равно хотите поймать и записать исключение определенного типа. Система, над которой я работал в прошлом, пусть бизнес-уровень ValidationExceptions, которые будут обнаружены на уровне представления. Эти исключения содержали информацию для проверки для отображения пользователю. Поскольку эти исключения будут пойманы и обработаны на уровне представления, они не будут пузыриться до самой верхней части приложения и не попадут в универсальный код приложения. Тем не менее, я хотел зарегистрировать эту информацию, просто чтобы узнать, как часто пользователь вводил неверную информацию, и чтобы узнать, были ли проверки вызваны по правильной причине. Так что это не было регистрацией ошибок; просто вход Я написал следующий код для этого:

try
{
   // some operations here.
}
catch (ValidationException ex)
{
    this.logger.Log(ex);
    throw;
}

Выглядит знакомо? Да, выглядит точно так же, как и предыдущий фрагмент кода, с той разницей, что я только поймал ValidationExceptions. Однако было еще одно отличие, которое нельзя увидеть, просто взглянув на фрагмент. В приложении было только одно место, содержащее этот код! Это был декоратор, который подводит меня к следующему вопросу, который вы должны задать себе:

2. Нарушаю ли я твердые принципы?

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

public void MoveCustomer(int customerId, Address newAddress)
{
    var watch = Stopwatch.StartNew();

    // Real operation

    this.logger.Log("MoveCustomer executed in " +
        watch.ElapsedMiliseconds + " ms.");
}

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

Проблема здесь является нарушением принципов SOLID. Принципы SOLID - это набор принципов объектно-ориентированного проектирования, которые помогут вам определить гибкое и поддерживаемое программное обеспечение. MoveCustomer Пример нарушил как минимум два из этих правил:

  1. Принцип единой ответственности. Класс проведения MoveCustomer Метод не только перемещает клиента, но и измеряет время, необходимое для выполнения операции. Другими словами, он имеет несколько обязанностей. Вы должны извлечь измерение в свой собственный класс.
  2. Принцип Открыто-Закрыто (OCP). Поведение системы должно быть в состоянии изменить без изменения какой-либо существующей строки кода. Когда вам также требуется обработка исключений (третья ответственность), вы (опять же) должны изменить MoveCustomer метод, который является нарушением OCP.

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

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

// The real thing
public class MoveCustomerCommand
{
    public virtual void MoveCustomer(int customerId, Address newAddress)
    {
        // Real operation
    }
}

// The decorator
public class MeasuringMoveCustomerCommandDecorator : MoveCustomerCommand
{
    private readonly MoveCustomerCommand decorated;
    private readonly ILogger logger;

    public MeasuringMoveCustomerCommandDecorator(
        MoveCustomerCommand decorated, ILogger logger)
    {
        this.decorated = decorated;
        this.logger = logger;
    }

    public override void MoveCustomer(int customerId, Address newAddress)
    {
        var watch = Stopwatch.StartNew();

        this.decorated.MoveCustomer(customerId, newAddress);

        this.logger.Log("MoveCustomer executed in " +
            watch.ElapsedMiliseconds + " ms.");
    }
}

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

MoveCustomerCommand command =
    new MeasuringMoveCustomerCommandDecorator(
        new MoveCustomerCommand(),
        new DatabaseLogger());

Предыдущий пример, однако, только что решил часть проблемы (только часть SOLID). При написании кода, как показано выше, вам нужно будет определить декораторы для всех операций в системе, и вы получите такие декораторы, какMeasuringShipOrderCommandDecorator, MeasuringCancelOrderCommandDecorator, а также MeasuringCancelShippingCommandDecorator, Это снова приводит к большому количеству повторяющегося кода (нарушение принципа СУХОЙ) и все еще требует написания кода для каждой операции в системе. Здесь не хватает общей абстракции над вариантами использования в системе. Чего не хватает, так это ICommandHandler<TCommand> интерфейс.

Давайте определим этот интерфейс:

public interface ICommandHandler<TCommand>
{
    void Execute(TCommand command);
}

И давайте сохраним аргументы метода MoveCustomer метод в свой собственный ( Parameter Object) класс называется MoveCustomerCommand:

public class MoveCustomerCommand
{
    public int CustomerId { get; set; }
    public Address NewAddress { get; set; }
}

И давайте поместим поведение MoveCustomer метод в классе, который реализует ICommandHandler<MoveCustomerCommand>:

public class MoveCustomerCommandHandler : ICommandHandler<MoveCustomerCommand>
{
    public void Execute(MoveCustomerCommand command)
    {
        int customerId = command.CustomerId;
        var newAddress = command.NewAddress;
        // Real operation
    }
}

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

public class MeasuringCommandHandlerDecorator<TCommand>
    : ICommandHandler<TCommand>
{
    private ICommandHandler<TCommand> decorated;
    private ILogger logger;

    public MeasuringCommandHandlerDecorator(
        ICommandHandler<TCommand> decorated, ILogger logger)
    {
        this.decorated = decorated;
        this.logger = logger;
    }

    public void Execute(TCommand command)
    {
        var watch = Stopwatch.StartNew();

        this.decorated.Execute(command);

        this.logger.Log(typeof(TCommand).Name + " executed in " +
            watch.ElapsedMiliseconds + " ms.");
    }
}

Это новый MeasuringCommandHandlerDecorator<T> выглядит очень похоже на MeasuringMoveCustomerCommandDecorator, но этот класс можно повторно использовать для всех обработчиков команд в системе:

ICommandHandler<MoveCustomerCommand> handler1 =
    new MeasuringCommandHandlerDecorator<MoveCustomerCommand>(
        new MoveCustomerCommandHandler(),
        new DatabaseLogger());

ICommandHandler<ShipOrderCommand> handler2 =
    new MeasuringCommandHandlerDecorator<ShipOrderCommand>(
        new ShipOrderCommandHandler(),
        new DatabaseLogger());

Таким образом, будет гораздо проще добавить сквозные проблемы в систему. Довольно легко создать удобный метод в вашем корне композиции, который может обернуть любой созданный обработчик команд соответствующими обработчиками команд в системе. Например:

ICommandHandler<MoveCustomerCommand> handler1 = 
    Decorate(new MoveCustomerCommandHandler());

ICommandHandler<ShipOrderCommand> handler2 =
    Decorate(new ShipOrderCommandHandler());

private static ICommandHandler<T> Decorate<T>(ICommandHandler<T> decoratee)
{
    return
        new MeasuringCommandHandlerDecorator<T>(
            new DatabaseLogger(),
                new ValidationCommandHandlerDecorator<T>(
                    new ValidationProvider(),
                    new AuthorizationCommandHandlerDecorator<T>(
                        new AuthorizationChecker(
                            new AspNetUserProvider()),
                        new TransactionCommandHandlerDecorator<T>(
                            decoratee))));
}

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

В настоящее время большинство современных DI-контейнеров для.NET имеют довольно приличную поддержку декораторов, и особенно Autofac ( пример) и Simple Injector ( пример) упрощают регистрацию открытых универсальных декораторов. Простой Injector даже позволяет декораторам применяться условно на основе заданного предиката или сложных ограничений общего типа, позволяет декорированному классу быть введенным как фабрика и позволяет контекстному контексту вводиться в декораторы, и все это может быть действительно полезным время от времени. время.

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

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

Надеюсь, это поможет.

Другие вопросы по тегам