Service Locator 是可插拔架构中的反模式吗?
Is Service Locator an anti pattern in a pluggable architecture?
我知道这个问题可能看起来是重复的,但请让我解释一下。
所以我创建了几个使用可插拔架构的组件,基本上我可以自由添加新的实现,它们会自动为我注入和处理。这在几种情况下真的很方便。
我要谈谈最简单的,验证组件。
使用这样的设计的原因之一是我喜欢明确地公开我的角色,正如 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)
我们将不胜感激您的想法
在我看来,您的代码示例的主要问题是服务定位器本身被注入到 ValidatorRunner 的实现中。对我来说,这是一种反模式,但也许不是您要问的那种模式。
我可能给出的任何答案都归结为您的服务定位器实现的能力。但可以肯定的是,它不应该传递到 class 的构造函数中。相反,当您要求服务定位器实现 "IValidatorRuner"
时,服务定位器本身应该传递这些东西
例如,您可以注入一个知道如何为给定类型加载动态验证器实例的工厂。
如果有人感兴趣,我找到了一种方法来删除我的对象中的 ServiceLocator,并且在 运行 时间仍然动态 load/discover 依赖关系。
我解决它的方法是通过以下方式(使用调解器模式)在我的 DI 容器中注册我的组件:
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>
,因为容器会为我创建一个.
好了,现在我很高兴,因为我从我的域对象中删除了服务定位器 =)
我知道这个问题可能看起来是重复的,但请让我解释一下。
所以我创建了几个使用可插拔架构的组件,基本上我可以自由添加新的实现,它们会自动为我注入和处理。这在几种情况下真的很方便。
我要谈谈最简单的,验证组件。
使用这样的设计的原因之一是我喜欢明确地公开我的角色,正如 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)
我们将不胜感激您的想法
在我看来,您的代码示例的主要问题是服务定位器本身被注入到 ValidatorRunner 的实现中。对我来说,这是一种反模式,但也许不是您要问的那种模式。
我可能给出的任何答案都归结为您的服务定位器实现的能力。但可以肯定的是,它不应该传递到 class 的构造函数中。相反,当您要求服务定位器实现 "IValidatorRuner"
时,服务定位器本身应该传递这些东西例如,您可以注入一个知道如何为给定类型加载动态验证器实例的工厂。
如果有人感兴趣,我找到了一种方法来删除我的对象中的 ServiceLocator,并且在 运行 时间仍然动态 load/discover 依赖关系。
我解决它的方法是通过以下方式(使用调解器模式)在我的 DI 容器中注册我的组件: 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>
,因为容器会为我创建一个.
好了,现在我很高兴,因为我从我的域对象中删除了服务定位器 =)