Виндзор-вытягивание переходных объектов из контейнера


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

еще одна мысль, которая пришла ко мне была "новая" их по мере необходимости. Но я также впрыскиваю 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 объект без использования "new"?

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

1   51   2012-03-27 18:59:42

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. Нарушаю ли я твердые принципы?

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

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 принципы. Твердые принципы представляют собой набор объектно-ориентированных принципов проектирования, которые помогают вам в определении гибкого и ремонтопригодного программного обеспечения. Элемент MoveCustomer пример нарушил по крайней мере два из этих правил:

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

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

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

// 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());

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

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

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

и давайте сохраним аргументы метода MoveCustomer метод в свой собственный (Объект класс) называется 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());

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

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))));
}

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

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

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

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

я надеюсь, что это помогает.