如何将 "Composite Design Pattern" 与 Ninject 一起使用

How to use "Composite Design Pattern" with Ninject

验证规则合约:

public interface IValidationRule
{
    bool IsValid();
}

具体验证规则:

public class MyClass : IValidationRule
{
    public bool IsValid()
    {
        return true;
    }
}

复合:

public class ValidationRuleComposite : IValidationRule
{
    private readonly IEnumerable<IValidationRule> _validationRules;

    public ValidationRuleComposite(IEnumerable<IValidationRule> validationRules)
    {
        _validationRules = validationRules;
    }

    public bool IsValid()
    {
        return _validationRules.All(x => x.IsValid());
    }
}

当我向容器询问 IValidationRule 时,我想得到 ValidationRuleComposite。如果我向容器询问 IValidationRule 的列表,我想获得 IValidationRule 的所有实现,除了 ValidationRuleComposite.

如何使用 Ninject 实现此目的?

我不知道如何直接使用 Ninject 执行此操作,但您可以使用 Ninject 创建一个 class,然后创建您的验证规则。

public class ValidationRuleFactory : IValidationRuleFactory
{
    public IValidationRule CreateComposite()
    {
        var rules = CreateRules();
        return new ValidationRuleComposite(rules);
    }

    private IEnumerable<IValidationRule> CreateRules()
    {
        //return all other rules here.
        //I would hard code them and add new ones here as they are created.
        //If you don't want to do that you could use reflection.
    }
}

因为这个 class 没有任何状态,您可以使用单例范围创建它。

kernel.Bind<IValidationRuleFactory>().To<ValidationRuleFactory>().InSingletonScope();

然后你注入这个class并用它来创建你的复合

public class MyClass()
{
    private readonly IValidationRuleFactory _validationRuleFactory;

    public MyClass(IValidationRuleFactory validationRuleFactory)
    {
        _validationRuleFactory = validationRuleFactory;
    }

    public bool CheckValid()
    {
        var composite = _validationRuleFactory.CreateComposite();
        return composite.IsValid();
    }
}

像这样在 Ninject 中连接 ValidationRule 的具体实例。

this.Kernel.Bind<ValidationRule1>().ToSelf();
this.Kernel.Bind<ValidationRule2>().ToSelf();
this.Kernel.Bind<IValidationRule>().To<ValidationRuleComposite>()
    .WithConstructorArgument("validationRules", 
        new IValidationRule[] { 
            this.Kernel.Get<ValidationRule1>(), 
            this.Kernel.Get<ValidationRule2>() 
        });

现在,只要您有一个在其构造函数中采用 IValidationRule 的服务,您将获得注入了 ValidationRule1ValidationRule2ValidationRuleComposite 具体类型。

据我所知,Ninject 在注入多个相同类型的实例时表现不佳。在这种情况下,我们 避免 这样做,因此解析 IValidationRule 总是导致复合类型。

但是,您可以使用自动查找所有类型的反射构建自己的扫描约定,排除名称中具有后缀 "Composite" 的任何类型,然后遍历这些类型以首先将它们绑定到自身然后创建一个要注入的实例数组。看看 this example of a custom scanning implementation, and its usage.

这里我假设您需要所有验证规则,而不是它们的部分列表,按照更通用的模式。 我会稍微更改 Composition class 以便您可以执行

kernel.Get<IValidationRuleComposite>()

还有一个

kernel.GetAll<IValidationRule>()

下面是一个简单的例子。 接口

public interface IValidationRule
{
    bool IsValid();
}
public interface IValidationRuleComposite : IValidationRule
{
    void ValidationRuleCompose(List<IValidationRule> validationRules);
}

和规则

public class MyClass1 : IValidationRule
{
    public bool IsValid()
    {
        Debug.WriteLine("Valid 1");
        return true;
    }
}
public class MyClass2 : IValidationRule
{
    public bool IsValid()
    {
        Debug.WriteLine("Valid 2");
        return false;
    }
}

复合规则

public class ValidationRuleComposite : IValidationRuleComposite
{

private List<IValidationRule> _validationRules;
public void ValidationRuleCompose(List<IValidationRule> validationRules)
{
    _validationRules = _validationRules.Union(validationRules).ToList();
}
public ValidationRuleComposite()
{
    _validationRules = new List<IValidationRule>();
}
public bool IsValid()
{
    Debug.WriteLine("Composite Valid");
    return _validationRules.All(x => x.IsValid());

}

}

和一个主要

        StandardKernel kernel = new StandardKernel();
        kernel.Bind<IValidationRule>().To<MyClass1>();
        kernel.Bind<IValidationRule>().To<MyClass2>();
        kernel.Bind<IValidationRuleComposite>().To<ValidationRuleComposite>();

        IValidationRuleComposite try1 = kernel.Get<IValidationRuleComposite>();

        IEnumerable<IValidationRule> rules = kernel.GetAll<IValidationRule>();
        foreach(IValidationRule trycomp in rules)
            { Debug.WriteLine("trycomp: " + trycomp.GetType().ToString()); trycomp.IsValid(); };

        try1.ValidationRuleCompose(rules.ToList());
        Console.WriteLine("{0}",try1.IsValid());
        Debug.WriteLine("try1: " + try1.GetType().ToString());

编辑

等效替代方案,保留复合构造函数

public interface IValidationRuleCompositeConstr : IValidationRule
{

}
public class ValidationRuleCompositeOriginal : IValidationRuleCompositeConstr
{
    private readonly IEnumerable<IValidationRule> _validationRules;

    public ValidationRuleCompositeOriginal(IEnumerable<IValidationRule> validationRules)
    {
        _validationRules = validationRules;
    }

    public bool IsValid()
    {
        return _validationRules.All(x => x.IsValid());
    }
}

对应用法:

    StandardKernel kernel = new StandardKernel();
    kernel.Bind<IValidationRule>().To<MyClass1>();
    kernel.Bind<IValidationRule>().To<MyClass2>();
    kernel.Bind<IValidationRuleCompositeConstr>().To<ValidationRuleCompositeOriginal>();

    IEnumerable<IValidationRule> rules = kernel.GetAll<IValidationRule>();
    Ninject.Parameters.ConstructorArgument therules = new Ninject.Parameters.ConstructorArgument("therules", rules);
        IValidationRuleCompositeConstr try2 = kernel.Get<IValidationRuleCompositeConstr>(therules);
        Debug.WriteLine("Second Class");
        Debug.WriteLine (string.Format("{0}",try2.IsValid()));

首先,您要为将注入组合的 IEnumerable 设置绑定。您可以单独绑定它们:

// Bind all the individual rules for injection into the composite
kernel.Bind<IValidationRule>().To<MyClass>().WhenInjectedInto<ValidationRuleComposite>();
kernel.Bind<IValidationRule>().To<RuleTwo>().WhenInjectedInto<ValidationRuleComposite>();

或者您也可以使用 convention binding extensions 相当轻松地设置 IEnumerable,这样您就不必为每个单独的具体规则添加单独的绑定。只需确保为复合 class 添加 Exlcuding 子句,如下所示:

using Ninject.Extensions.Conventions;

// Bind all the non-composite IValidationRules for injection into ValidationRuleComposite
kernel.Bind(x => x.FromAssemblyContaining(typeof(ValidationRuleComposite))
    .SelectAllClasses()
    .InheritedFrom<IValidationRule>()
    .Excluding<ValidationRuleComposite>()
    .BindAllInterfaces()
    .Configure(c => c.WhenInjectedInto<ValidationRuleComposite>()));

在我的示例中,复合材料和其余混凝土位于同一个程序集中,但很明显,如果它们在其他地方,您可以改变约定绑定。

最后,我们需要设置绑定,以便在其他任何地方请求 IValidationRule,Ninject 提供组合。似乎没有一个优雅的方法可以解决这个问题,所以我写了自己的 When 子句来避免循环注入:

// Now bind the composite to the interface for everywhere except itself
kernel.Bind<IValidationRule>().To<ValidationRuleComposite>()
    .When(x => x.Target == null
          || x.Target.Member.ReflectedType != typeof(ValidationRuleComposite));

在 Soldarnal 的帮助下,我找到了以下解决方案:

public static class KernelExtensions
{
    public static void BindComposite<TComposite, TCompositeElement>(this StandardKernel container) where TComposite : TCompositeElement
    {
        container.Bind(x => x.FromAssemblyContaining(typeof(TComposite))
            .SelectAllClasses()
            .InheritedFrom<TCompositeElement>()
            .Excluding<TComposite>()
            .BindAllInterfaces()
            .Configure(c => c.WhenInjectedInto<TComposite>()));

        container.Bind<TCompositeElement>().To<TComposite>()
          .When(IsNotCompositeTarget<TComposite>);
    }

    private static bool IsNotCompositeTarget<TComposite>(IRequest x)
    {
        if (x.Target == null)
            return true;
        return x.Target.Member.ReflectedType != typeof(TComposite);
    }
}

用法:

var container = new StandardKernel();
container.BindComposite<ValidationRuleComposite, IValidationRule>();