温莎 – 从容器中拉出瞬态物体

我怎样才能从容器中取出瞬态的物体呢? 我是否需要在容器中注册它们并注入需要的类的构造函数? 将所有内容注入构造函数并不好。 也只是对于一个类,我不想创建一个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对象?

  2. QueueMonitorSingleton ,而Data是“Scoped”。 如何将Data注入OnDataReceived()方法?

从你给的样本很难具体,但一般来说,当你将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解决方案是不同的,但是这个概念保持不变:定义一个最顶级的捕获所有。

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

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

看起来很熟悉? 是的,看起来和前面的代码片段完全一样,区别在于我只捕获了ValidationException 。 然而,还有另外一个不同点,那就是只看片段就看不到。 包含该代码的应用程序中只有一个地方! 这是一个装饰者,这让我想到下一个问题,你应该问自己:

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操作所需的时间,并记录该信息。 系统中的其他操作很可能需要这种相同的交叉关注。 您将开始为您的ShipOrderCancelOrderCancelShipping等添加这样的代码。这样做会导致大量的代码重复,最终导致维护噩梦。

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

  1. 单一责任原则 。 持有MoveCustomer方法的类不仅可以移动客户,还可以测量执行操作所需的时间。 换句话说,它有多重责任。 你应该把测量值提取到自己的类中。
  2. 开放 – 封闭原则 (OCP)。 系统的行为应该能够被改变,而不改变任何现有的代码行。 当您还需要异常处理(第三个责任)时,您(再次)必须更改MoveCustomer方法,这违反了OCP。

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

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

 // 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部分)。 如上所示编写代码时,您必须为系统中的所有操作定义装饰器,并最终使用诸如MeasuringShipOrderCommandDecoratorMeasuringCancelOrderCommandDecoratorMeasuringCancelShippingCommandDecorator类的装饰器。 这又导致了很多重复的代码(违反了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()); 

通过这种方式,将更多的交叉担忧添加到系统中会容易得多。 在Composition 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)))); } 

如果你的应用程序开始增长,那么在没有容器的情况下引导这个可能会很痛苦。 特别是当你的装饰器具有泛型类型约束。

但是有一个问题。 Unity和Windsor配置装饰器似乎更加困难。 Autofac( 示例 )和Simple Injector( 示例 )使注册开放通用装饰器变得更加容易。 Simple Injector甚至允许装饰者根据给定的谓词或复杂的泛型类型约束条件应用,允许装饰类作为工厂注入,并允许上下文上下文注入装饰器,所有这些都可以从时间时间。

团结和城堡另一方面有拦截设施(如Autofac做btw)。 拦截与装饰有许多共同之处,但它使用动态代理生成。 这可以比使用通用装饰器更灵活,但是当涉及到可维护性时,您将付出代价,因为您经常会失去类型安全性,拦截器总是强制您依赖拦截库,而装饰器是类型安全的可以在不依赖外部库的情况下编写。

阅读这篇文章,如果你想了解更多关于这种设计你的应用程序的方式: 同时…在我的架构的命令端 。

我希望这有帮助。