温莎 - 从容器拉临时对象 [英] Windsor - pulling Transient objects from the container

查看:131
本文介绍了温莎 - 从容器拉临时对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我如何从拉是一过性的容器中的对象?我一定要与容器注册这些注入的需要类的构造函数?一切都注入到构造手感并不好。也只有一类,我不希望创建一个 TypedFactory 和工厂注入到需要的类。

这是来找我的另一个想法是新起来的必要基础。但我也注入日志组件(通过属性)为我所有的类。所以,如果我新的他们,我将不得不手动实例日志在这些类中。我怎样才能继续使用该容器为我所有的班?

记录器注入:我的大部分课程都在日志属性除非有继承链中,(在这种情况下,只有基类具有这种特性,并且所有的派生类使用)。当这些都通过温莎容器实例,他们会得到我的执行 ILogger 注射到他们。

  //安装QueueMonitor作为单身
Container.Register(Component.For&其中; QueueMonitor>()LifestyleSingleton());
//安装数据处理器为Trnsient
Container.Register(Component.For&其中;数据处理器>()LifestyleTransient());

Container.Register(Component.For<数据>()LifestyleScoped());

公共类QueueMonitor
{
    私人数据处理器;

    公共ILogger记录器{获得;组; }

    公共无效OnDataReceived(数据资料)
    {
        //从工厂拉数据处理器
        dataProcessor.ProcessData(数据);
    }
}

公共类数据处理器
{
    公共ILogger记录器{获得;组; }

    公共记录[]过程数据(数据资料)
    {
        //数据可以有多个记录
        //循环遍历数据,并创造新的记录集
        //这是为了创造新的纪录正确的方法是什么?
        //我如何使用的容器在这里和避免新
        录制录制=新记录(/ *使用数据* /);
        ...

        //返回记录列表
    }
}


公共类记录
{
    公共ILogger记录器{获得;组; }

    私人_recordNumber;
    私人_recordOwner;

    公共字符串GetDescription()
    {
        Logger.LogDebug(登录的东西);
        //返回定制描述
    }
}
 

问题:

  1. 如何创建新的记录对象,而不使用新?

  2. QueueMonitor 辛格尔顿,而数据在作用域。我怎么可以注入数据 OnDataReceived()的方法?

解决方案

从样品,你给它很难做到非常具体,但在一般情况下,当你注入 ILogger 实例到大多数的服务,你应该问自己两件事情:

  1. 请登录我的太多了?
  2. 请我违反了SOLID原则?

1。难道我登录了太多

正在记录太多,当你有很多code是这样的:

 尝试
{
   //此处一些操作。
}
赶上(例外前)
{
    this.logger.Log(前);
    扔;
}
 

写作code像这样来自丢失的错误信息的关注。然而,复制这些类型的try-catch块的所有的地方,并没有帮助。更糟糕的是,我经常看到开发者登录并继续(它们删除最后一个抛出语句)。这是非常糟糕的(闻起来像老VB 出错时继续下一步),因为在大多数情况下,你根本就没有足够的信息来确定它是否是安全的继续。经常有在导致操作失败的code的错误。继续意味着用户往往得到的想法,操作成功,而它也没有。问问你自己:更糟糕的是,显示用户的一般错误消息说,有什么出了错,或者静静地跳过错误,让用户觉得他的请求被成功处理?想想用户会感觉如何,如果他发现了两个星期后,他的命令是从来没有发货。你可能会失去一个客户。更糟的是,患者的 MRSA 注册默默的失败,从而导致不​​被护士被隔离,并导致污染病人其他患者,导致成本高或者甚至死亡。

大多数这类的try-catch-日志行应该被删除,您应该简单地让异常泡了调用堆栈。

如果你不记录?你绝对应该!但是,如果可以的话,定义一个try-catch块在应用程序的顶部。在ASP.NET,您可以实现的Application_Error 事件,注册 HTTP模块或定义自定义错误页,做的日志记录。与Win表单解决方案有所不同,但概念保持不变:定义一个单独的顶级最包罗万象

但是有时候,你仍然想捕捉和记录一个特定类型的异常。系统我在过去的工作,让我们的业务层抛出 ValidationException s,这将捕获的presentation层。这些例外包含验证信息以显示给用户。因为这些异常会被逮住,并在presentation层处理,他们也不会冒泡到应用程序的最上面的部分,并没有结束,在应用程序的包罗万象的code。不过我想记录这些信息,只需找出多久用户输入无效信息,并找出是否在那里引发了正当的理由的验证。所以这是没有错误记录;只是记录。我写了下面code要做到这一点:

 尝试
{
   //此处一些操作。
}
赶上(ValidationException前)
{
    this.logger.Log(前);
    扔;
}
 

看起来很熟悉?是的,看起来完全一样的previous code段,与我只抓到 ValidationException S中的差异。然而,还有一个区别,那不能只看该片段中可以看出。有中包含了code中的应用程序只有一个地方!这是一个装饰,这使我下一个问题你应该问自己:

2。我是否违反了SOLID原则?

之类的东西日志,审计和安全性,被称为横切关注(或问题)。它们被称为横切,因为它们可以跨越应用程序的多个层,并且必须经常被应用到许多类在系统中。但是,当你发现你写code供他们使用的许多类在系统中,你最有可能违反了SOLID原则。就拿下面的例子:

 公共无效MoveCustomer(INT客户ID,地址newAddress)
{
    VAR手表= Stopwatch.StartNew();

    //实际操作

    this.logger.Log(MoveCustomer在执行+
        watch.ElapsedMiliseconds +毫秒。);
}
 

下面我们测量才能执行 MoveCustomer 操作的时间,我们记录这些信息。这是非常可能的是系统中的其他操作都需要此相同的横切关注。你会开始增加code这样你的的ShipOrder CancelOrder CancelShipping 等方法,最终这导致了很多code复制,并最终维护的噩梦。

这里的问题是违反了 SOLID 的原则。 SOLID原则是一组面向对象的设计原则,帮助你定义灵活的和可维护的软件。该 MoveCustomer 例如侵犯至少两种这些规则:

  1. 单一职责原则。类持有 MoveCustomer 方法不仅移动客户,而且还能测量它需要做手术的时间。换句话说,它具有多个责任。你应该提取测量到它自己的类。
  2. 开 - 闭原则(OCP)。该系统的行为应该能够在不改变code任何现有生产线被改变。当你还需要异常处理(第三责任),你(再次)必须改变 MoveCustomer 方法,这是违反了OCP的。

除了违反了SOLID原则我们肯定违反了原则在这里,这基本上说,code重复是坏的,mkay。

解决这个问题的方法是提取记录到它自己的类和允许类来包装的原班:

  //真实的东西
公共类MoveCustomerCommand
{
    公共虚拟无效MoveCustomer(INT客户ID,地址newAddress)
    {
        //实际操作
    }
}

//该装饰
公共类MeasuringMoveCustomerCommandDecorator:MoveCustomerCommand
{
    私人只读MoveCustomerCommand装饰;
    私人只读ILogger记录;

    公共MeasuringMoveCustomerCommandDecorator(
        MoveCustomerCommand装饰,ILogger记录仪)
    {
        this.decorated =装饰;
        this.logger =记录;
    }

    公众覆盖无效MoveCustomer(INT客户ID,地址newAddress)
    {
        VAR手表= Stopwatch.StartNew();

        this.decorated.MoveCustomer(客户ID,newAddress);

        this.logger.Log(MoveCustomer在执行+
            watch.ElapsedMiliseconds +毫秒。);
    }
}
 

通过缠绕真实情况的装饰,你现在可以添加此测量行为的类,没有系统的任何其他部分改变:

  MoveCustomerCommand命令=
    新MeasuringMoveCustomerCommandDecorator(
        新MoveCustomerCommand(),
        新DatabaseLogger());
 

在previous例子也不过只是解决问题(只固体部分)的一部分。当编写C如上图所示的$ C $,你必须定义装饰为系统中的所有操作,而你最终会与像装饰 MeasuringShipOrderCommandDecorator MeasuringCancelOrderCommandDecorator MeasuringCancelShippingCommandDecorator 。再这导致了大量的重复的code(违反了DRY原则),仍然需要写code为系统中的每一个操作。现在缺少的这里是过度使用的情况下在系统中通用的抽象。现在缺少的是一个 ICommandHandler< TCommand> 接口

让我们来定义这个接口:

 公共接口ICommandHandler< TCommand>
{
    无效执行(TCommand命令);
}
 

和我们的存储 MoveCustomer 方法的方法参数到它自己(参数对象)类叫做 MoveCustomerCommand

 公共类MoveCustomerCommand
{
    公众诠释客户ID {获得;组; }
    公共广播NewAddress {获得;组; }
}
 

和让我们把在一个类中的 MoveCustomer 法的行为,实现 ICommandHandler< MoveCustomerCommand>

 公共类MoveCustomerCommandHandler:ICommandHandler< MoveCustomer>
{
    公共无效执行(MoveCustomer命令)
    {
        诠释客户ID = command.CustomerId;
        VAR newAddress = command.NewAddress;
        //实际操作
    }
}
 

这看起来很奇怪,但因为我们现在对用例的一般抽象,我们可以重写我们的装饰如下:

 公共类MeasuringCommandHandlerDecorator< TCommand>
    :ICommandHandler< TCommand>
{
    私人ICommandHandler< TCommand>装饰;
    私人ILogger记录;

    公共MeasuringCommandHandlerDecorator(
        ICommandHandler< TCommand>装饰,ILogger记录仪)
    {
        this.decorated =装饰;
        this.logger =记录;
    }

    公共无效执行(TCommand命令)
    {
        VAR手表= Stopwatch.StartNew();

        this.decorated.Execute(命令);

        this.logger.Log(typeof运算(TCommand).Name点+,在执行+
            watch.ElapsedMiliseconds +毫秒。);
    }
}
 

这个新的 MeasuringCommandHandlerDecorator&LT; T&GT; 看起来很像 MeasuringMoveCustomerCommandDecorator ,但是这个类可以重复使用的<​​strong >所有系统中的命令处理程序:

  ICommandHandler&LT; MoveCustomerCommand&GT; handler1 =
    新MeasuringCommandHandlerDecorator&LT; MoveCustomerCommand&GT;(
        新MoveCustomerCommandHandler(),
        新DatabaseLogger());

ICommandHandler&LT; ShipOrderCommand&GT; handler2 =
    新MeasuringCommandHandlerDecorator&LT; ShipOrderCommand&GT;(
        新ShipOrderCommandHandler(),
        新DatabaseLogger());
 

此方式,将是非常非常容易增加横切关注到系统中。这是很容易地创建一个方便的方法在成分根,可以换任何创建命令处理程序的系统适用的命令处理程序。例如:

  ICommandHandler&LT; MoveCustomerCommand&GT; handler1 =
    装点(新MoveCustomerCommandHandler());

ICommandHandler&LT; ShipOrderCommand&GT; handler2 =
    装点(新ShipOrderCommandHandler());

私有静态ICommandHandler&LT; T&GT;装饰&LT; T&GT;(ICommandHandler&LT; T&GT; decoratee)
{
    返回
        新MeasuringCommandHandlerDecorator&LT; T&GT;(
            新DatabaseLogger(),
                新ValidationCommandHandlerDecorator&LT; T&GT;(
                    新ValidationProvider(),
                    新AuthorizationCommandHandlerDecorator&LT; T&GT;(
                        新AuthorizationChecker(
                            新AspNetUserProvider()),
                        新TransactionCommandHandlerDecorator&LT; T&GT;(
                            decoratee))));
}
 

如果您的应用程序启动但是成长,它可以让痛苦来引导这一切,没有一个容器。尤其是当您装饰有泛型类型的限制。

但有一个陷阱,但。它似乎是更难以配置装饰用<一href="https://stackoverflow.com/questions/9813630/how-to-do-open-generic-decorator-chaining-with-unity-unityautoregistration">Unity和<一href="https://stackoverflow.com/questions/12637216/how-can-i-register-a-generic-decorator-using-castle-windsor">Windsor. Autofac(例如)和简单注射器(的example )使它更容易注册开放式泛型装饰。简单的喷油器甚至允许装饰有条件地根据应用在一个给定的predicate或复杂的泛型类型的限制,允许装饰类是<一个href="https://simpleinjector.readthedocs.org/en/latest/advanced.html#decorators-with-func-t-decoratee-factories">injected作为工厂并允许<一href="https://simpleinjector.readthedocs.org/en/latest/advanced.html#using-contextual-information-inside-decorators">contextual背景的拟注入装饰,所有这一切都可能是真正有用的,不时。

团结和城堡,另一方面有拦截设施(如Autofac确实给BTW)。拦截有很多共同的装饰,但它使用在幕后动态代理生成。这可能是比一般的装饰工作更灵活,但你会付出代价的,当涉及到可维护性,因为你会经常松散型的安全和拦截总是迫使你采取的拦截库的依赖性,而装饰是类型安全可以写成不以依赖于外部库。

阅读这篇文章,如果您想了解更多关于这种方式设计应用程序的:同时。 ..在我的建筑指挥方。

我希望这有助于。

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.

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

Questions:

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

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

解决方案

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. Do I log too much?
  2. Do I violate the SOLID principles?

1. Do I log too much

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

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.

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

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.

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

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. Do I violate the SOLID principles?

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

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.

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. 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.

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

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.

Let's define this interface:

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

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

And let's put the behavior of the MoveCustomer method in a class that implements ICommandHandler<MoveCustomerCommand>:

public class MoveCustomerCommandHandler : ICommandHandler<MoveCustomer>
{
    public void Execute(MoveCustomer 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.");
    }
}

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.

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 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.

I hope this helps.

这篇关于温莎 - 从容器拉临时对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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