当注入的对象是有条件的时,C# IoC 实例化

C# IoC Instantiation when the injected objects are conditional

我有一个目前比较抽象的 IoC 问题。我还没有选择一个 IoC 框架来开始编码。我仍在心里计划我将要用于一个即将到来的项目的方法。

我的编码风格通常遵循以下模式:

我的问题是这样的。例如,我不喜欢我的 AccessLayer 反过来需要从配置文件中提取连接字符串。也许我希望我的用户能够为设置指定配置文件或 Db Table。让访问层检查配置文件以查看它是否应该使用配置文件是循环和愚蠢的。并且访问层同样不能调用持久性对象来提取设置,或查询应用程序框架以查看它是具有 Web.Config 的 Web 应用程序还是具有 DbSettings 的桌面应用程序。

所以我认为对我来说最好的办法是使用某种 IoC 容器。然后我可以注入我需要的任何设置。这也可以让我模拟对象进行测试,这是我当前方法的另一个困难(但并非不可能)的任务。所以根据我的阅读,我模糊的处理器实现看起来像这样:

public class VagueProcessor{
   public VagueProcessor(IValidator validator, 
                         IPersistence persistence, 
                         IAccessLayer accessLayer,
                         ISettings settings) { ... }
}

这是我的障碍。在我计划的应用程序中,业务对象有多种实现,每个实现都有自己的可配置规则。假设一个 BO 适用于 CA 州,另一个适用于 NY 州,这两个州都有自己的特殊规则,需要由其管理机构验证。因此,验证器可以是 CAValidator 或 NYValidator,这取决于业务对象的状态。

好的,所以在所有序言和背景故事之后我的问题是:在这种情况下,我是否会将 ValidatorFactory 传递给处理器,而工厂会根据业务对象的状态实例化适当类型的验证器?如果是这样,我会向 IoC 容器注册每种类型,还是只向工厂注册?

感谢您对此事的看法!!

这是一个模糊的问题,因为你还没有问题,只有想法。

根据我从你的问题中了解到的情况,我会说:

  1. IOC解决的是创建新对象的问题,而不是确切地决定创建哪个对象。在大多数 IOC 容器中,您可以在某种程度上选择您所要求的实现,但在您的情况下,逻辑看起来非常以应用程序为中心,并且没有 IOC 容器可以帮助您决定使用哪个。在那种情况下,您确实应该将一个工厂传递给您的处理器,您可以在其中询问 factory.CreateValidatorFrom(myBusinessObject).

  2. 在内部,该工厂仍然可以使用 DI 来实例化每个组件。例如,如果您使用 .NET Core DI,则可以将 IServiceProvider 传递给工厂,并在工厂内部调用 serviceProvider.GetService<CAValidator>()。所有 DI 提供程序都会有一个这样的对象。

所以从某种意义上说,工厂和DI是可以共存的,各自解决一部分问题。如果您使用的是 DI,则永远不必实例化实际的 class。这将使每个验证器更容易拥有自己的依赖项,您不必关心如何获取它们。

是的,在这种情况下,您需要在 DI 和工厂中注册每个验证器。在这种情况下,您可以轻松地通过反射遍历所有这些并通过名称或接口动态注册它们,如果这让您感到困扰。

最后,如果您使用的是 .NET Core,我强烈建议您只使用内置的 DI。对于大多数情况来说,它简单且足够好。

验证是 crosscutting concern,因此验证服务通常不知道它正在验证的对象的详细信息。它只知道其 布尔有效状态 以及如何 获取通常显示在 UI.[=18= 上的验证错误 ]

作为横切关注点,验证规则是从读取它们的服务中抽象出来的。这通常是通过接口 and/or .NET 属性完成的。

public class ValidateMe : IValidatableObject
{
    [Required]
    public bool Enable { get; set; }

    [Range(1, 5)]
    public int Prop1 { get; set; }

    [Range(1, 5)]
    public int Prop2 { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (!this.Enable)
        {
            /* Return valid result here.
            * I don't care if Prop1 and Prop2 are out of range
            * if the whole object is not "enabled"
            */
        }
        else
        {
             /* Check if Prop1 and Prop2 meet their range requirements here
             * and return accordingly.
             */ 
        }
    }
}

验证服务只需要有一个机制来处理规则(为每个规则返回一个true/false)以确保它们所有都是有效的,以及检索显示错误的方法。

验证服务只需将模型(运行时状态)传递给服务即可完成所有这些工作。

if (validationService.IsValid(model));
{
    // persist
}

这也可以使用 proxy pattern 来完成,以确保在接口 and/or 属性可用于处理时始终发生。

NOTE: The term Business Object implies that you want to build some sort of Smart Object Framework using objects that know how to save and retrieve their own state (internally implementing CRUD). This sort of design doesn't lend itself to DI very well. That isn't to say you can't use DI and a Smart Object design at the same time, it is just more difficult to build, more difficult to test, and then more difficult to maintain.

A design that uses models to abstract the runtime state of the application away from the services that use the models makes for an easier path. A design that I have found works pretty well for some applications is Command Query Segregation, which turns every update or request for data into its own object. It works well with a proxy or a decorator pattern to implement crosscutting concerns. It sounds strange if you are used to working with smart objects, but a loosely coupled design like this is simpler to test which makes it just as reliable, and since query and command classes are used like

var productDetails = this.queryProcessor.Execute(new GetProductDetailsQuery
{
     ProductId = id
});

// This command executes a long and complicated workflow,
// but this is all that is done inside of the action method
var command = new AddToCartCommand
{
    ProductId = model.Id,
    Quantity = model.Qty,
    Selections = model.Selections,
    ShoppingCartId = this.anonymousIdAccessor.AnonymousID
};

this.addToCartHandler.Handle(command);

it is almost as easy to use. You can even easily break out different steps of a complicated workflow into their own commands so it can be tested and verified at each step of the way, which is something that is difficult to do on a smart object design.