如何避免服务定位器反模式?
我正在尝试从抽象基类中删除服务定位器,但我不确定用什么替换它。这是我所得到的一个伪示例:
public abstract class MyController : Controller
{
protected IKernel kernel;
public MyController(IKernel kernel) { this.kernel = kernel); }
protected void DoActions(Type[] types)
{
MySpecialResolver resolver = new MySpecialResolver(kernel);
foreach(var type in types)
{
IMyServiceInterface instance = resolver.Get(type);
instance.DoAction();
}
}
}
此问题 是派生类的实例化器不知道内核必须具有哪些绑定才能防止 MySpecialResolver
引发异常。
这可能本质上是棘手的,因为我不知道我必须解决哪些类型。派生类负责创建 types
参数,但它们没有在任何地方进行硬编码。 (这些类型基于派生类的组合层次结构深处的属性的存在。)
我尝试使用延迟加载委托来解决此问题,但到目前为止我还没有提出一个干净的解决方案。
更新
这里确实有两个问题,一是 IoC 容器被传递到控制器,充当服务定位器。这很容易删除——您可以使用各种技术在调用堆栈中向上或向下移动该位置。
第二个问题是困难的,当需求直到运行时才暴露时,如何确保控制器具有必要的服务。从一开始就应该很明显:你不能!您将始终依赖于服务定位器的状态或集合的内容。在这种特殊情况下,再多的摆弄也无法解决本文 具有静态类型依赖项。我认为我最终要做的是将一个惰性数组传递到控制器构造函数中,并在缺少所需依赖项时抛出异常。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
发布评论
评论(3)
在发布这个答案之前,我本想了解更多信息,但凯利让我陷入了困境。 :) 可以这么说,告诉我把代码放在我想说的地方。
正如我在对 Kelly 的评论中所说,我不同意将解析器/定位器从静态实现转移到注入实现。我同意 ChrisChris 的观点,即派生类型所需的依赖关系应该在该类中解决,而不是委托给基类。
也就是说,这是我删除服务位置的方法...
创建命令接口
首先,我将为特定实现创建一个命令接口。在本例中,使用 DoActions 方法发送的类型是从属性生成的,因此我将创建一个 IAttributeCommand
。我向命令添加了一个 Matches
方法,以便声明该命令供某些类型使用。
public interface IAttributeCommand
{
bool Matches(Type type);
void Execute();
}
添加命令实现
为了实现该接口,我传入了执行命令所需的特定依赖项(由我的容器解析)。我向 Matches 方法添加一个谓词,并定义我的执行行为。
public class MyTypeAttributeCommand : IAttributeCommand
{
MyDependency dependency;
SomeOtherDependency otherDependency;
public MyTypeAttributeCommand (MyDependency dependency, ISomeOtherDependency otherDependency)
{
this.dependency = dependency;
this.otherDependency = otherDependency
}
public bool Matches(Type type)
{
return type==typeof(MyType)
}
public void Execute()
{
// do action using dependency/dependencies
}
}
使用容器注册命令
在 StructureMap 中(使用您最喜欢的容器),我将像这样注册数组:
Scan(s=>
{
s.AssembliesFromApplicationBaseDirectory();
s.AddAllTypesOf<IAttributeCommand>();
s.WithDefaultConventions();
}
根据类型选择并执行命令
最后,在基类上,我在构造函数中定义一个 IAttributeCommand
数组由 IOC 容器注入的参数。当派生类型传入 types
数组时,我将根据谓词执行正确的命令。
public abstract class MyController : Controller
{
protected IAttributeCommand[] commands;
public MyController(IAttributeCommand[] commands) { this.commands = commands); }
protected void DoActions(Type[] types)
{
foreach(var type in types)
{
var command = commands.FirstOrDefault(x=>x.Matches(type));
if (command==null) continue;
command.Execute();
}
}
}
如果多个命令可以处理一种类型,则可以更改实现: commands.Where(x=>x.Matches(type)).ToList().ForEach(Execute);
效果是相同,但类的构造方式有细微的差别。该类与 IOC 容器没有耦合,并且没有服务位置。该实现更具可测试性,因为可以使用其真正的依赖项来构造类,而无需连接容器/解析器。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
我同意@chrisichris 和@Mark Seemann 的观点。
从控制器中删除内核。我会稍微改变你的解析器组成,以便你的控制器可以删除对 IoC 容器的依赖,并允许解析器成为唯一担心 IoC 容器的项目。
然后我会让解析器传递到控制器的构造函数中。这将使您的控制器更具可测试性。
例如:
现在你的控制器没有耦合到特定的 IoC 容器。此外,您的控制器更易于测试,因为您可以模拟解析器,并且根本不需要 IoC 容器来进行测试。
或者,如果您无法控制实例化控制器的时间,则可以稍微修改它:
然后您可以在应用程序启动时调用它来初始化解析器:
我们这样做是为了处理在 XAML 中创建的需要解析依赖项的元素但我们想删除类似请求的服务定位器。
请原谅任何语法错误:)
我正在写一篇博客文章系列,主题是在您可能感兴趣的视图模型中使用服务定位器调用重构 MVVM 应用程序。第 2 部分即将推出:)
"="">http://kellabyte.com/2011/07/24/refactoring-to-improve-maintainability-and-blendability-using-ioc-part-1-view-models/
I agree with @chrisichris and @Mark Seemann.
Ditch the kernel from the controller. I'd switch your resolver composition a little bit so that your controller can remove the dependency on the IoC container and allow the resolver to be the only item that worries about the IoC container.
Then I would let the resolver get passed into the constructor of the controller. This will allow your controller to be far more testable.
For example:
Now your controller isn't coupled to a specific IoC container. Also your controller is much more testable since you can mock the resolvers and not require an IoC container at all for your tests.
Alternatively, if you don't get to control when a controller is instantiated, you can modify it slightly:
You would then call this at your application start up to initialize the resolver:
We did this to handle elements created in XAML who require dependencies resolved but we wanted to remove Service Locator like requests.
Please excuse any syntactical errors :)
I'm writing a blog post series on the topic of refactoring an MVVM application with Service Locator calls in the view models you might find interesting. Part 2 is coming soon :)
http://kellabyte.com/2011/07/24/refactoring-to-improve-maintainability-and-blendability-using-ioc-part-1-view-models/