如何避免服务定位器反模式?

发布于 11-26 14:09 字数 1211 浏览 3 评论 0 原文

我正在尝试从抽象基类中删除服务定位器,但我不确定用什么替换它。这是我所得到的一个伪示例:

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 容器被传递到控制器,充当服务定位器。这很容易删除——您可以使用各种技术在调用堆栈中向上或向下移动该位置。

第二个问题是困难的,当需求直到运行时才暴露时,如何确保控制器具有必要的服务。从一开始就应该很明显:你不能!您将始终依赖于服务定位器的状态或集合的内容。在这种特殊情况下,再多的摆弄也无法解决本文 具有静态类型依赖项。我认为我最终要做的是将一个惰性数组传递到控制器构造函数中,并在缺少所需依赖项时抛出异常。

I'm trying to remove a Service Locator from an abstract base class, but I'm not sure what to replace it with. Here is a psuedo-example of what I've got:

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

The problem with this is that the instanciator of a derived class doesn't know what bindings the kernel must have in order to keep MySpecialResolver from throwing an exception.

This might be intrinsicly intractable because I don't know from here which types I'll have to resolve. The derived classes are responsible for creating the types parameter, but they aren't hardcoded anywhere. (The types are based on the presence of attributes deep within the derived class's composition hierarchy.)

I've trying to fix this with lazy loading delegates, but so far I haven't come up with a clean solution.

Update

There are really two issues here, one is that the IoC container is passed to the controller, acting as a service locator. This is easy to remove--you can move the location up or down the call stack using all sorts of techniques.

The second issue is the difficult one, how can you ensure that the controller has the necessary services when the requirements aren't exposed until runtime. It should have been obvious from the start: you can't! You will always be dependent upon either the state of the service locator or contents of a collection. In this particular case no amount of fiddling will ever resolve the problem described in this article with staticly typed dependencies. I think that what I'm going to end up doing is passing a Lazy array into the controller constructor and throwing an exception if a required dependency is missing.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

后知后觉 2024-12-03 14:09:45

我同意@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 中创建的需要解析依赖项的元素但我们想删除类似请求的服务定位器。

请原谅任何语法错误:)

我正在写一篇博客文章系列,主题是在您可能感兴趣的视图模型中使用服务定位器调用重构 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:

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

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:

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

You would then call this at your application start up to initialize the resolver:

MyController.InitializeResolver(new NinjectMyServiceResolver(kernal));

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/

白云悠悠 2024-12-03 14:09:45

也许您应该删除 Kernel、Types 和 MySpecialResolver,并让子类直接使用它们需要的 IMyServiceInterface 实例作为参数来调用 DoActions。让子类决定如何到达这些实例 - 他们应该最了解(或者万一他们不知道到底是哪个人决定需要哪些 IMyServiceInterface 实例)

Maybe you should just do away the Kernel, Types and MySpecialResolver and let the subclasses call DoActions with the IMyServiceInterface instances they need as argument directly. And let the subclasses decide how they get to these instances - they should know best (or in case they don't know which exactly the one who ever decides which instances of IMyServiceInterface are needed)

客…行舟 2024-12-03 14:09:45

在发布这个答案之前,我本想了解更多信息,但凯利让我陷入了困境。 :) 可以这么说,告诉我把代码放在我想说的地方。

正如我在对 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 容器没有耦合,并且没有服务位置。该实现更具可测试性,因为可以使用其真正的依赖项来构造类,而无需连接容器/解析器。

I would have liked to have a bit more information before posting this answer, but Kelly put me on the spot. :) Telling me to put my code where my mouth is, so to speak.

Like I said in my comment to Kelly, I disagree with moving the resolver/locator from a static implementation to an injected implementation. I agree with ChrisChris that the dependencies the derived type needs should be resolved in that class and not delegated to the base class.

That said, here is how I would remove the service location...

Create Command Interface

First of all I would create a command interface for the specific implementation. In this case the types sent with the DoActions method are generated from attributes, so I would create an IAttributeCommand. I am adding a Matches method to the command in order to declare the command for use by certain types.

public interface IAttributeCommand
{
    bool Matches(Type type);
    void Execute();
}

Add Command Implementations

To implement the interface, I pass in the specific dependencies I need to execute my command (to be resolved by my container). I add a predicate to my Matches method, and define my Execute behavior.

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

Register Commands with Container

In StructureMap (use your favorite container), I would register the array like so:

Scan(s=>
       {
                s.AssembliesFromApplicationBaseDirectory();
                s.AddAllTypesOf<IAttributeCommand>();
                s.WithDefaultConventions();
       } 

Select and Execute Commands Based on Type

Finally, on the base class, I define an IAttributeCommand array in my constructor arguments to be injected by the IOC container. When the derived type passes in the types array, I will execute the correct command based on the predicate.

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

If you multiple commands can handle one type, you can change the implementation: commands.Where(x=>x.Matches(type)).ToList().ForEach(Execute);

The effect is the same, but there is a subtle difference in how the class is constructed. The class has no coupling to an IOC container and there is no service location. The implementation is more testable as the class can be constructed with its real dependencies, with no need to wire up a container/resolver.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文