注入的对象是有条件的

时间:2018-02-04 10:23:04

标签: c# dependency-injection inversion-of-control

我有一个IoC问题,暂时是抽象的。我还没有为开始编码选择IoC框架。我仍在精神上计划我将用于即将开展的项目的方法。

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

  • 实例化某种处理器并传递业务对象。
  • 处理器将依次实例化Validator以验证传递的业务对象对给定进程是否有效。
  • 如果发现业务对象有效,则将实例化持久对象。 Persistence对象负责转换,例如加密,缓存以及在对象图的单个事务中将多个请求组合在一起。
  • 然后,业务对象实例化一个DataLayer,它将把业务对象持久化到数据库,或者根据具体情况从数据库中提取它(或者文本文件或web服务,无论数据是什么)住。)
  • 我理想的结构是处理器知道Validator和Peristence对象,但不知道AccessLayer。持久性对象知道访问层,但不能直接实例化或调用进程。这种方式有明确定义的层,可以根据需要分开
  • 最后,此过程与输入或输出无关,并且基于应用程序类型不可变。换句话说,我可以使用相同的处理器在Web应用程序中添加业务对象,就像在桌面应用程序中一样。显然,模型/视图/控制器会根据应用类型而改变,但添加或选择业务对象的规则仍然是通用的。

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

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

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

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

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

感谢您对此事的想法!!

2 个答案:

答案 0 :(得分:1)

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

根据我的理解,我会说:

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

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

  3. 因此,从某种意义上说,工厂和DI可以共存,每个都可以解决部分问题。如果你正在使用DI,你不应该实例化实际的类。这将使每个验证器更容易拥有自己的依赖关系,而您不必关心如何获取它们。

    是的,在这种情况下,你会在DI和工厂注册每个验证器。在这种情况下,您可以通过反射轻松遍历所有这些,并通过名称或界面动态注册它们,如果那样困扰您。

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

答案 1 :(得分:1)

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

作为横切关注点,验证规则是从读取它们的服务中抽象出来的。这通常通过接口和/或.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来完成,以确保在接口和/或属性可用于处理时始终发生。

  

注意:术语业务对象意味着您希望使用知道如何保存的对象构建某种智能对象框架检索自己的状态(内部实现CRUD)。这种设计并不适合DI。这并不是说你不能同时使用DI和智能对象设计,它更难以构建,更难以测试,然后更难以维护。

     

使用模型抽象应用程序的运行时状态远离使用模型的服务的设计使得路径更容易。我发现一些适用于某些应用程序的设计是Command Query Segregation,它将每次更新或请求数据转换为自己的对象。它适用于代理或装饰器模式以实现横切关注点。如果您习惯使用智能对象,这听起来很奇怪,但是像这样松散耦合的设计更容易测试,这使得它同样可靠,并且因为使用了查询和命令类,如

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

它几乎同样易于使用。您甚至可以轻松地将复杂工作流程的不同步骤分解为自己的命令,以便在每个步骤中对其进行测试和验证,这在智能对象设计中很难实现。