服务定位器是可插拔架构中的反模式吗?

时间:2015-02-18 01:35:45

标签: c# plugins dependency-injection cqrs service-locator

我知道这个问题可能看起来像是重复但请让我解释一下。

所以我创建了几个使用可插拔架构的组件,基本上我可以自由添加新的实现,它们将自动注入和处理。这在几种情况下都非常方便。

我要谈谈最简单的一个,验证组件。

使用这样的设计的一个原因是我喜欢明确地公开我的角色,如Udi Dahan所述

基本上我有这样的代码:

public interface IValidatorRuner
{
    void Run<TTarget>(TTarget target);
}

public class ValidatorRunenr : IValidatorRuner
{
    private readonly IServiceLocator _serviceLocator;

    public ValidatorRunenr(IServiceLocator serviceLocator)
    {
        _serviceLocator = serviceLocator;
    }

    public void Run<TTarget>(TTarget target)
    {
        // this is the dynamic/pluggable phase
        // is this an antipattern???
        var foundValdiators = _serviceLocator.GetAllInstances<IValidator<TTarget>>();

        foreach (var valdiator in foundValdiators)
        {
            valdiator.IsSatisfiedBy(target);
        }
    }
}

此代码允许我明确地公开我的验证规则:

//this will allow me to create validators in this way
//and they will be automatically injected and resolved for me 
//(easy, to read, easy to write, easy to test, pff I could even smoke this validator easily)
public class OneValdiationRuleExplicitlyExposedAndEasyToTest : IValidator<Person>
{
    public bool IsSatisfiedBy(Person target)
    {
        return target.Age > 18;
    }
}

public class Person
{
    public int Age { get; set; }
}

public interface IValidator<TTarget>
{
    bool IsSatisfiedBy(TTarget target);
}

我将使用这样的代码:

//usage
public class SomeCommandHandler
{
    private readonly IValidatorRuner _validatorRuner;

    public SomeCommandHandler(IValidatorRuner validatorRuner)
    {
        _validatorRuner = validatorRuner;
    }

    public void SomeMethod()
    {
        _validatorRuner.Run(new Person{Age = 16});
    }
}

验证只是一个例子,我也用它来触发域事件并以相同的可插入方式运行管道和过滤器

以这种方式使用服务定位器是一种反模式吗?

我知道我可能隐藏了一些依赖项,但问题是在应用程序初始化时会动态注入和发现依赖项(Composition root

您的想法将受到高度赞赏

2 个答案:

答案 0 :(得分:0)

在我看来,您的代码示例的主要问题是服务定位器本身被注入ValidatorRunner的实现中。对我来说,这是一种反模式,但也许不是你要问的那种。

我可能给出的任何答案归结为服务定位器实现的功能。但是肯定不应该将它传递给你的类的构造函数。相反,当你要求它实现&#34; IValidatorRuner&#34;

时,服务定位器本身应该传递这些内容。

例如,您可以注入一个知道如何为给定类型加载动态验证器实例的工厂。

答案 1 :(得分:0)

如果有人感兴趣,我找到了一种方法来删除对象中的ServiceLocator,并在运行时仍然动态加载/发现依赖项。

我解决它的方法是通过以下方式在我的DI容器中注册我的组件(使用Mediator模式): Binding mediator (shortbus) with/to ninject

var kernel = new StandardKernel();

kernel.Bind(x => x.FromThisAssembly()
.SelectAllClasses()
.InheritedFromAny(
    new[]
    {
        typeof(IValidatorRunner<>)
    })
.BindDefaultInterfaces());

我的最终实现如下:

public interface IValidatorRuner<in TTarget>
{
    void Run(TTarget target);
}

public class ValidatorRunenr<TTarget> : IValidatorRuner<TTarget>
{
    private readonly IEnumerable<IValidator<TTarget>> _validators;

    public ValidatorRunenr(IEnumerable<IValidator<TTarget>> validators)
    {
        _validators = validators;
    }

    public void Run(TTarget target)
    {
        foreach (var valdiator in _validators)
        {
            valdiator.IsSatisfiedBy(target);
        }
    }
}

用法

//usage
public class SomeCommandHandler
{
    private readonly IValidatorRuner<OneValdiationRuleExplicitlyExposedAndEasyToTest> _validatorRuner;

    public SomeCommandHandler(IValidatorRuner<OneValdiationRuleExplicitlyExposedAndEasyToTest> validatorRuner)
    {
        _validatorRuner = validatorRuner;
    }

    public void SomeMethod()
    {
        _validatorRuner.Run(new Person{Age = 16});
    }
}

简而言之,通过注册打开的泛型类型,我的容器会解析对该类型的任何调用,为我在运行时创建具体的封闭泛型类型实例。

正如您在中看到的,我不必创建特定的具体封闭泛型类型IValidatorRunner<OneValdiationRuleExplicitlyExposedAndEasyToTest>,因为容器会为我创建一个。

你去了,现在我很高兴,因为我从我的域对象中删除了服务定位器=)