我正在尝试从抽象基类中删除服务定位器,但我不确定要用什么来替换它。这是我所得到的一个假例子:
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();
}
}
}
problem with this是派生类的instanciator不知道内核必须具有什么绑定才能防止MySpecialResolver
抛出异常。
这可能是内在难以处理的,因为我从这里不知道我必须解决哪些类型。派生类负责创建types
参数,但它们在任何地方都不是硬编码的。 (这些类型基于派生类的组合层次结构中深层属性的存在。)
我试图通过延迟加载代理修复此问题,但到目前为止,我还没有提出一个干净的解决方案。
这里确实存在两个问题,一个是IoC容器传递给控制器,充当服务定位器。这很容易删除 - 您可以使用各种技术在调用堆栈中向上或向下移动位置。
第二个问题是困难的问题,如果要求在运行时才暴露,如何确保控制器具有必要的服务。它应该从一开始就很明显:你做不到!您将始终依赖于服务定位器的状态或集合的内容。在这种特殊情况下,任何数量的小问题都不会解决this article中描述的具有静态类型依赖关系的问题。我认为我最终要做的是将一个Lazy数组传递给控制器构造函数,并在缺少必需的依赖项时抛出异常。
答案 0 :(得分:4)
也许您应该放弃Kernel,Types和MySpecialResolver,让子类直接使用他们需要的IMyServiceInterface实例来调用DoActions。让子类决定他们如何到达这些实例 - 他们应该最了解(或者如果他们不知道究竟哪个人决定需要哪个IMyServiceInterface实例)
答案 1 :(得分:4)
我同意@chrisichris和@Mark Seemann。
从控制器中删除内核。我会稍微切换你的解析器组合,以便你的控制器可以删除对IoC容器的依赖,并允许解析器成为唯一担心IoC容器的项目。
然后我会让解析器传递给控制器的构造函数。这将使您的控制器更加可测试。
例如:
public interface IMyServiceResolver
{
List<IMyServiceInterface> Resolve(Type[] types);
}
public class NinjectMyServiceResolver : IMyServiceResolver
{
private IKernal container = null;
public NinjectMyServiceResolver(IKernal container)
{
this.container = container;
}
public List<IMyServiceInterface> Resolve(Type[] types)
{
List<IMyServiceInterface> services = new List<IMyServiceInterface>();
foreach(var type in types)
{
IMyServiceInterface instance = container.Get(type);
services.Add(instance);
}
return services;
}
}
public abstract class MyController : Controller
{
private IMyServiceResolver resolver = null;
public MyController(IMyServiceResolver resolver)
{
this.resolver = resolver;
}
protected void DoActions(Type[] types)
{
var services = resolver.Resolve(types);
foreach(var service in services)
{
service.DoAction();
}
}
}
现在您的控制器未连接到特定的IoC容器。此外,您的控制器更易于测试,因为您可以模拟解析器并且根本不需要IoC容器来进行测试。
或者,如果您无法控制何时实例化控制器,您可以稍微修改它:
public abstract class MyController : Controller
{
private static IMyServiceResolver resolver = null;
public static InitializeResolver(IMyServiceResolver resolver)
{
MyController.resolver = resolver;
}
public MyController()
{
// Now we support a default constructor
// since maybe someone else is instantiating this type
// that we don't control.
}
protected void DoActions(Type[] types)
{
var services = resolver.Resolve(types);
foreach(var service in services)
{
service.DoAction();
}
}
}
然后,您可以在应用程序启动时调用它来初始化解析器:
MyController.InitializeResolver(new NinjectMyServiceResolver(kernal));
我们这样做是为了处理在XAML中创建的元素,这些元素需要解析依赖项,但我们想要删除服务定位器之类的请求。
请原谅任何语法错误:)
我正在撰写一篇关于在您可能感兴趣的视图模型中使用Service Locator调用重构MVVM应用程序主题的博客文章系列。第2部分即将推出:)
答案 2 :(得分:1)
我希望在发布这个答案之前有更多的信息,但凯利把我当场。 :)告诉我把我的代码放在嘴边,可以这么说。
就像我在对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容器的耦合,也没有服务位置。该实现更具可测性,因为可以使用其真正的依赖关系构建类,而无需连接容器/解析器。