How to use “Composite Design Pattern” with Ninject

感情迁移 提交于 2019-12-07 13:38:26

问题


Validation Rule Contract:

public interface IValidationRule
{
    bool IsValid();
}

Concrete Validation Rule:

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

Composite:

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

When I ask the containter for IValidationRule I want to get ValidationRuleComposite. If I ask the container for a list of IValidationRule I want to get all implementations of IValidationRule except of the ValidationRuleComposite.

How can I achieve this with Ninject?


回答1:


First you want to set up the bindings for the IEnumerable<IValidationRule> that will be injected into the composite. You can just bind them individually:

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

Or you can also setup the IEnumerable fairly easy with the convention binding extensions, so that you don't have to add a separate binding for each individual concrete rule. Just be sure to add the Exlcuding clause for the composite class like so:

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

In my example the composite and the rest of the concretes are in the same assembly, but obviously you can vary your convention binding if they're somewhere else.

Finally, we need to set up the binding so that everywhere else an IValidationRule is request, Ninject provides the composite. There doesn't seem to be an elegant method existing for this, so I wrote my own When clause to avoid the cyclical injection:

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



回答2:


With the help of Soldarnal I came to the following solution:

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

Usage:

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



回答3:


Here I'm assuming that you want all the validation rules and not a partial list of them, as per the more generic pattern. I would slightly change the Composition class so that you can do a

kernel.Get<IValidationRuleComposite>()

and a

kernel.GetAll<IValidationRule>()

A simple example follows. The interfaces

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

and the rules

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

The composite rule

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

}

}

and a main

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

EDIT

Equivalent alternative, preserving your composite constructor

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

with corresponding usage:

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



回答4:


I don't know how you could do that directly with Ninject, but you could use Ninject to create a class which then creates your validation rules.

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

as this class doesn't hold any state you can then create it with singleton scope.

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

Then you inject this class and use it to create your composite

public class MyClass()
{
    private readonly IValidationRuleFactory _validationRuleFactory;

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

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



回答5:


You wire up your concrete instances of ValidationRule in Ninject, like this.

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

Now, whenever you have a service that takes an IValidationRule in its constructor, you will get the ValidationRuleComposite concrete type with both ValidationRule1 and ValidationRule2 injected.

As far as I know, Ninject doesn't play nice when it comes to injecting multiple instances of the same type. In this case, we avoid doing that so resolving IValidationRule always results in the composite type.

However, you could build your own scanning convention using Reflection that automatically finds all of the types, excludes any that have the suffix "Composite" in the name, then loops through the types to first bind them to self and then create an array of instances to inject. Have a look at this example of a custom scanning implementation, and its usage.



来源:https://stackoverflow.com/questions/28692658/how-to-use-composite-design-pattern-with-ninject

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!