Windsor - 从容器中拉出瞬态物体 [英] Windsor - pulling Transient objects from the container

查看:229
本文介绍了Windsor - 从容器中拉出瞬态物体的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何从本质上暂时的容器中拉出物体?我必须向容器注册并注入需要类的构造函数?将所有内容注入构造函数并不理想。也只是一个类,我不想创建一个 TypedFactory 并将工厂注入需要的类。

How can I pull objects from the container that are transient in nature? Do I have to register them with the container and inject in the constructor of the needing class? Injecting everything into the constructor doesn't feel good. Also just for one class I don't want to create a TypedFactory and inject the factory into the needing class.

Logger 组件(通过属性)。所以如果我新建它,我将不得不手动实例化这些类中的 Logger 。我如何继续使用所有课程的容器?

Another thought that came to me was "new" them up on need basis. But I am also injecting a Logger component (through property) into all my classes. So if I new them up, I will have to manually instantiate the Logger in those classes. How can I continue to use the container for ALL of my classes?

记录器注入: 我的大部分课程具有定义的 Logger 属性,除非有继承链(在这种情况下,只有基类具有此属性,并且所有派生类都使用该属性)。当这些通过Windsor容器实例化时,他们会将我的执行 ILogger 注入它们。

Logger injection: Most of my classes have the Logger property defined, except where there is inheritance chain (in that case only the base class has this property, and all the deriving classes use that). When these are instantiated through Windsor container, they would get my implementation of ILogger injected into them.

//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. 如何创建新的记录对象而不使用new ?

  1. How do I create new Record object without using "new"?

QueueMonitor Singleton 数据是Scoped。如何将数据注入 OnDataReceived()方法?

QueueMonitor is Singleton, whereas Data is "Scoped". How can I inject Data into OnDataReceived() method?


推荐答案

从您给出的样本很难非常具体,但一般来说,当您注入 ILogger 实例进入大多数服务,您应该问自己两件事:

From the samples you give it is hard to be very specific, but in general, when you inject ILogger instances into most services, you should ask yourself two things:


  1. 我记录过多吗? li>
  2. 我是否违反SOLID原则?

1。我记录太多

当你有很多这样的代码时,你记录得太多了:

You are logging too much, when you have a lot of code like this:

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

编写这样的代码来自丢失错误信息的关注。在这些地方复制这些try-catch块,但没有帮助。更糟糕的是,我经常看到开发人员登录并继续(他们删除最后一个 throw 语句)。这是非常糟糕的(并闻起来像旧的VB ON ERROR RESUME NEXT ),因为在大多数情况下,你根本没有足够的信息来确定它是否安全继续。通常,代码中存在导致操作失败的错误。继续意味着用户经常会得到操作成功的想法,而没有。问问自己:更糟糕的是,向用户显示一个通用错误消息,指出出现了错误,或者默默地跳过错误,并让用户认为他的请求已成功处理?想想如果两周后他发现他的订单从未发货,用户将会感觉到如何。你可能会失去一个客户。或者更糟的是,患者的 MRSA 注册静静地失败,导致患者不被护理隔离并导致其他患者的污染,导致高成本或甚至死亡。

Writing code like this comes from the concern of losing error information. Duplicating these kinds of try-catch blocks all over the place however, doesn't help. Even worse, I often see developers log and continue (they remove the last throw statement). This is really bad (and smells like the old VB ON ERROR RESUME NEXT), because in most situations you have simply not enough information to determine whether it is safe continue. Often there is a bug in the code that caused the operation to fail. To continue means that the user often gets the idea that the operation succeeded, while it hasn't. Ask yourself: what is worse, showing the user a generic error message saying that there something gone wrong, or silently skipping the error and letting the user think his request was successfully processed? Think about how the user will feel if he found out two weeks later that his order was never shipped. You’d probably lose a customer. Or worse, a patient’s MRSA registration silently fails, causing the patient not to be quarantined by nursing and resulting in the contamination of other patients, causing high costs or perhaps even death.

大多数这些try-catch-log行应该被删除,你应该简单让这个例外起来调用堆栈。

Most of these kinds of try-catch-log lines should be removed and you should simply let the exception bubble up the call stack.

你不应该记录?你绝对应该!但是如果可以,在应用程序的顶部定义一个try-catch块。使用ASP.NET,您可以实现 Application_Error 事件,注册一个 HttpModule 或定义自定义错误页面日志记录。使用Win Forms,解决方案是不同的,但概念保持不变:定义一个单一的最全面的。

Shouldn't you log? You absolutely should! But if you can, define one try-catch block at the top of the application. With ASP.NET, you can implement the Application_Error event, register a HttpModule or define a custom error page that does the logging. With Win Forms the solution is different, but the concept stays the same: Define one single top most catch-all.

有时候,您仍然希望捕获并记录某种类型的异常。一个我过去工作的系统,让业务层抛出 ValidationException s,这将被表示层捕获。这些例外包含用于显示给用户的验证信息。由于这些异常会在表示层中被捕获和处理,所以它们不会冒泡到应用程序的大部分部分,并没有最终在应用程序的全部代码中。仍然我想记录这些信息,只是为了找出用户输入无效信息的频率,并确定是否为正确的原因触发的验证。所以这没有错误记录;只是登录。我写了以下代码:

Sometimes however, you still want to catch and log a certain type of exception. A system I worked on in the past, let the business layer throw ValidationExceptions, which would be caught by the presentation layer. Those exceptions contained validation information for display to the user. Since those exceptions would get caught and processed in the presentation layer, they would not bubble up to the top most part of the application and didn't end up in the application's catch-all code. Still I wanted to log this information, just to find out how often the user entered invalid information and to find out whether the validations where triggered for the right reason. So this was no error logging; just logging. I wrote the following code to do this:

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

看起来很熟悉?是的,看起来与以前的代码片段完全相同,区别在于我只捕获了 ValidationException s。然而,还有另一个区别,只是看看这段代码就无法看出。应用程序中只有一个地方包含该代码!这是一个装饰师,它带给我你应该问自己的下一个问题:

Looks familiar? Yes, looks exactly the same as the previous code snippet, with the difference that I only caught ValidationExceptions. However, there was another difference, that can't be seen by just looking at the snippet. There was only one place in the application that contained that code! It was a decorator, which brings me to the next question you should ask yourself:

2。我是否违反SOLID原则?

像日志,审计和安全性这样的东西称为交叉关切(或方面)。它们被称为交叉切割,因为它们可以跨越应用程序的多个层次,并且通常应用于系统中的许多类。但是,当您发现您正在为系统中的许多课程编写代码,您很可能违反SOLID原则。以下例子为例:

Things like logging, auditing, and security, are called cross-cutting concerns (or aspects). They are called cross-cutting, because they can cut across multiple layers of your application and must often be applied to many classes in the system. However, when you find you're writing code for their use in many classes in the system, you are most likely violating the SOLID principles. Take for instance the following example:

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 等方法结束这导致了很多代码重复,最终导致维护噩梦。

Here we measure the time it takes to execute the MoveCustomer operation and we log that information. It is very likely that other operations in the system need this same cross-cutting concern. You will start adding code like this for your ShipOrder, CancelOrder, CancelShipping, etc. methods end this leads to a lot of code duplication and eventually a maintenance nightmare.

这里的问题是违反了 SOLID 原则。 SOLID原则是一套面向对象的设计原则,可帮助您定义灵活和可维护的软件。 MoveCustomer 示例违反了至少两项规则:

The problem here is a violation of the SOLID principles. The SOLID principles are a set of object oriented design principles that help you in defining flexible and maintainable software. The MoveCustomer example violated at least two of those rules:


  1. 单一责任原则。持有 MoveCustomer 方法的类不仅可以移动客户,还可以测量执行操作所需的时间。换句话说,它有多重责任。您应该将测量提取到自己的课程中。

  2. 打开封闭原则(OCP)。应该能够改变系统的行为,而不用改变现有的代码行。当您还需要异常处理(第三责任)时,您(再次)必须更改 MoveCustomer 方法,这违反了OCP。

  1. The Single Responsibility Principle. The class holding the MoveCustomer method does not only move the customer, but also measures the time it takes to do the operation. In other words it has multiple responsibilities. You should extract the measuring into its own class.
  2. The Open-Closed principle (OCP). The behavior of the system should be able to be altered without changing any existing line of code. When you also need exception handling (a third responsibility) you (again) must alter the MoveCustomer method, which is a violation of the OCP.

除了违反SOLID原则,我们绝对违反了 DRY 原则,这基本上说代码重复是不好的,mkay。

Besides violating the SOLID principles we definitely violated the DRY principle here, which basically says that code duplication is bad, mkay.

解决这个问题的办法是将日志记录提取到自己的类中允许该类包装原始类:

The solution to this problem is to extract the logging into its own class and allow that class to wrap the original class:

// 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.");
    }
}

通过围绕真实实例包装装饰器,您可以现在将此测量行为添加到该类中,而不改变系统的任何其他部分:

By wrapping the decorator around the real instance, you can now add this measuring behavior to the class, without any other part of the system to change:

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

之前的例子只是解决了部分问题(只有SOLID部分)。在编写如上所示的代码时,您将必须为系统中的所有操作定义装饰器,最后会出现如
MeasuringShipOrderCommandDecorator MeasuringCancelOrderCommandDecorator MeasuringCancelShippingCommandDecorator 。这再次导致了很多重复的代码(违反DRY原则),并且仍然需要为系统中的每个操作编写代码。这里缺少的是对系统中的用例的常见抽象。缺少的是一个 ICommandHandler< TCommand> 界面。

The previous example did however just solve part of the problem (only the SOLID part). When writing the code as shown above, you will have to define decorators for all operations in the system, and you'll end up with decorators like MeasuringShipOrderCommandDecorator, MeasuringCancelOrderCommandDecorator, and MeasuringCancelShippingCommandDecorator. This lead again to a lot of duplicate code (a violation of the DRY principle), and still needing to write code for every operations in the system. What's missing here is an common abstraction over use cases in the system. What's missing is an ICommandHandler<TCommand> interface.

让我们定义这个界面:

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

我们来保存 MoveCustomer 方法转换为自己的(参数对象)类,名为 MoveCustomerCommand

And let's store the method arguments of the MoveCustomer method into its own (Parameter Object) class called 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
    }
}

奇怪的是,因为我们现在有一个用例的一般抽象,我们可以重写我们的装饰器,如下所示:

This might look strange, but because we now have a general abstraction for use cases, we can rewrite our decorator as follows:

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 ,但是这个类可以重用于系统中的所有命令处理程序:

This new MeasuringCommandHandlerDecorator<T> looks much like the MeasuringMoveCustomerCommandDecorator, but this class can be reused for all command handlers in the system:

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

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

这样一来,在系统中添加跨部门问题就会变得更加容易。在撰写根目录中创建一个方便的方法很容易,它可以包装任何创建命令处理程序与系统中适用的命令处理程序。例如:

This way it will be much, much easier to add cross-cutting concerns to the system. It's quite easy to create a convenient method in your Composition Root that can wrap any created command handler with the applicable command handlers in the system. For instance:

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

如果您的应用程序开始增长,那么可以让所有的没有容器。特别是当您的装饰器具有通用类型约束时。

If your application starts to grow however, it can get painful to bootstrap this all without a container. Especially when your decorators have generic type constraints.

但是有一个catch。使用 Unity Windsor 。自动曝光(示例)和简单注射器(示例)使更容易注册打开的通用装饰器。简单的注射器甚至允许装饰器根据给定的谓词或复杂的通用类型约束条件有条件地应用,允许装饰类为作为工厂注入,并允许上下文上下文注入装饰器中,所有这些都可以非常有用。

But there is one catch though. It seems to be more difficult to configure decorators with Unity and Windsor. Autofac (example) and Simple Injector (example) make it much easier to register open generic decorators. Simple Injector even allows decorators to be applied conditionally based on a given predicate or complex generic type constraints, allows the decorated class to be injected as a factory and allows contextual context to be injected into decorators, all of which can be really useful from time to time.

Unity和Castle另一方面具有拦截功能(如Autofac所示)。截取与装饰有很多共同点,但它使用动态代理生成在封面下。这可能比使用通用装饰器更灵活,但是在可维护性方面您将付出代价,因为您经常会松动类型安全性,并且拦截器总是强制您依赖于拦截库,而装饰器是安全的,可以在不依赖外部库的情况下编写。

Unity and Castle on the other hand have interception facilities (as Autofac does to btw). Interception has a lot in common with decoration, but it uses dynamic proxy generation under the covers. This can be more flexible than working with generic decorators, but you will pay the price when it comes to maintainability, because you will often loose type safety and interceptors always force you to take a dependency on the interception library, while decorators are type safe and can be written without taking a dependency on an external library.

如果您想了解有关设计应用程序的更多信息,请阅读本文:同时...在我的架构的命令端

Read this article if you want to learn more about this way of designing your application: Meanwhile... on the command side of my architecture.

我希望这有帮助。

这篇关于Windsor - 从容器中拉出瞬态物体的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆