7

我有一个 Web 服务,它将处理一些传入的数据(特别是来自 SharePoint 文档库的 InfoPath xml)。我目前正在使用 Ninject 来处理要加载的表单数据“策略”。这是一些代码(问题如下):

网络服务(入口点)

namespace Web.Services
{
    public bool AddForm(XmlDocument form, string formName)
    {
        IKernel kernel = new StandardKernel(new FormsModule());
        var ctx = kernel.Get<IPFormDataContext>(formName);

        return ctx.DoWork(form);
    }
}

Ninject 相关的东西

namespace Core.Modules
{
    public class FormsModule : NinjectModule
    {
        public override void Load()
        {
            Bind<IPFormDataContext>().ToSelf().Named("FormA");
            Bind<IPFormDataContext>().ToSelf().Named("FormB");
            // Snip

            Bind<IPFormDataStrategy>().To<FormAStratgey>()
                .WhenParentNamed("FormA");
            Bind<IPFormDataStrategy>().To<FormBStrategy>()
                .WhenParentNamed("FormB");
            // Snip
        }
    }
}

模式相关的东西

namespace Core.Forms
{
    public class IPFormDataContext
    {
        private IPFormDataStrategy _ipFormDataStrategy;

        public IPFormDataContext(IPFormDataStrategy strategy)
        {
            _ipFormDataStrategy = strategy;
        }

        public bool DoWork(XmlDocument form)
        {
            return _ipFormDataStrategy.DoWork(form);
        }
    }

    public abstract class IPFormDataStrategy
    {
        public abstract bool DoWork(XmlDocument form);
    }
}

namespace Core.Forms.FormStrategies
{
    class FormAStrategy : IPFormDataStrategy
    {
        public override bool DoWork(XmlDocument form)
        {
            // Deserialize form using (xsd.exe generated) FormAData
            // and perform some operation on the resulting data.
            return resultOfWork;
        }
    }
}

FormBStrategy 与我未列出的其他 7 种策略大致相同。我正在尝试找到一种方法将表单 xml 传递给 web 服务,并根据传入的表单类型调用正确的表单反序列化。

上面的代码“有效”;但感觉就像我在 Ninject 做某种服务地点,从我读到的内容来看这是一件坏事。但我想不出一个合适的方法来完成这个。对于使用 Ninject 或任何 IOC/DI 框架,我并没有死心塌地。

我在做什么......错了吗?我可以指出正确的方向吗?

4

3 回答 3

3

我不喜欢的两件事:

  1. 在方法内部创建内核AddForm。这绝不应该发生,因为它颠倒了 IoC 模式——相反,AddForm所属的类应该请求它需要的任何依赖项。
  2. 魔术弦的使用。AddForm()要求消费者发送一个命名策略的字符串似乎是不对的。

我不太确定如何解决这个问题;想到的一件事是向Func<string, IPFormDataStrategy>拥有的类添加依赖项AddForm(称为 X 类)。我在想象这样的事情:

namespace Web.Services
{
    public class X
    {
        private readonly Func<string, IPFormDataStrategy> _strategyResolver;

        public X(Func<string, IPFormDataStrategy> strategyResolver)
        {
            _strategyResolver = strategyResolver;
        }

        public bool AddForm(XmlDocument form, string formName)
        {
            return _strategyResolver(formName).DoWork(form);
        }
    }
}

然后你可以使用ToMethod绑定Func<string, IPFormDataStrategy>

public class FormsModule : NinjectModule
{
    public override void Load()
    {
        Bind<FormAStrategy>().ToSelf();
        Bind<FormBStrategy>().ToSelf();

        Bind<Func<string, IPFormDataStrategy>>().ToMethod(context => 
            new Func<string, IPFormDataStrategy>(formName => {
                switch (formName)
                {
                    case "FormA": 
                        return context.Kernel.Get<FormAStrategy>();
                        // Note, could also simply "return new FormAStrategy()" here.
                    case "FormB": 
                        return context.Kernel.Get<FormBStrategy>();
                    default: 
                        throw new InvalidOperationException(formName + " is unrecognized");
                }
            })
        );
    }
}

您可能会发现这不必要地复杂,也许它是......我喜欢它,因为它使类 X 的依赖关系显式化(即,获取一个给定表单名称的策略),而不是让它访问整个内核。这种方法还将获取策略的逻辑整合到单个 switch 语句中。它仍然依赖于魔术字符串,但我不知道如何解决这个问题,不知道更多的上下文......

于 2013-08-06T19:06:18.467 回答
3

如果您在示例代码中显示的类是准确的(即没有更多的方法和属性)。然后最简单的解决方案可能会起作用,您可以摆脱许多类/对类的依赖。

一个不依赖框架/容器的简单解决方案是:

public static class FormsProcessing
{
    private static ConcurrentDictionary<string, Func<FormProcessor>> _registeredProcessors = new ConcurrentDictionary<string, Func<FormProcessor>>();

    public delegate bool FormProcessor(XmlDocument form);

    public static void RegisterProcessor(string formKey, Func<FormProcessor> formsProcessorFactory)
    {
        _registeredProcessors.AddOrUpdate(formKey, formsProcessorFactory, (k, current) => formsProcessorFactory);
    }

    public static FormProcessor GetProcessorFor(string formKey)
    {
        Func<FormProcessor> processorFactory;
        if (_registeredProcessors.TryGetValue(formKey, out processorFactory);
            return processorFactory();
        return null;
    }

    public static bool Process(string formKey, XmlDocument form)
    {
        var processor = GetProcessorFor(formKey);
        if (null == processor)
            throw new Exception(string.Format("No processor for '{0}' forms available", formKey));
        return processor(form);
    }
}

用法:

namespace Web.Services
{
    public class MyServiceClass
    {
        public bool AddForm(XmlDocument form, string formName)
        {
            return FormsProcessing.Process(formName, form);
        }
    }
}

它简单明了,不需要或暴露对某些结构IPFormDataContextIPFormDataStrategy类的任何依赖。您拥有的唯一显式依赖项是对具有FormProcessor签名的委托。

与容器类似,您需要在某处执行注册:

FormsProcessing.RegisterProcessor("FormA", () => new FormAStrategy().DoWork);
FormsProcessing.RegisterProcessor("FormB", () => new FormBStrategy().DoWork);

或者,通过扫描程序集的约定(例如接口签名)来添加某种形式的(基于约定的)自动注册会很容易。

于 2013-08-26T17:33:55.127 回答
1

服务定位器通常是一种反模式,但重要的是要理解为什么它是一种反模式。原因通常与重构和类型安全有关。我认为确定您是否做错了什么的最好方法是将问题简化为绝对最简单的要求,然后判断到达那里的最简单途径。

据我了解,您的要求是:

  1. 在 Web 服务中接收 xml 表单数据
  2. 根据表单类型,调用相应的逻辑

我对你的进一步问题是:

  1. 您如何识别表单类型?它是xml文档中的一个字段吗?(如果是这样,使用它)
  2. 表单类型是否可能经常更改?

当它归结为它时,您必须使用某种标识符。正如@McGarnagle 指出的那样,魔术字符串可能与代码不同步。您可以使用 Strategy 类的类型名称,但它具有相同的“可能不同步”问题。

如果表单类型不太可能改变,只需使用 switch 语句并自己实例化 Strategy 实例。简单性是可维护性的最佳设计模式。

如果它们可能发生变化,则服务定位器可以工作。如果您的服务定位器实现仅限于此代码,那么维护起来不会那么糟糕。

在 Ninject 上,我不确定这个基准测试是否仍然有效,但Funq更快,而且我更喜欢语法。(如果你决定使用 DI 容器)

于 2013-08-26T17:19:04.727 回答