我有一个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);
}
}
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传递给webservice,并根据正在进入的表单类型调用正确的表单反序列化。
上面的代码“有效”;但感觉我在Ninject做了某种服务,从我正在阅读的是坏事。但我想不出一个正确的方法来实现这一目标。我并没有立即使用Ninject或任何IOC / DI框架。
我在做什么......错了?我可以指出正确的方向吗?
答案 0 :(得分:3)
我不喜欢的两件事:
AddForm
方法中创建内核。这应该永远不会发生,因为它会颠倒IoC模式 - 相反,AddForm
所属的类应该请求它所需的任何依赖。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语句的逻辑。它仍然依赖于神奇的弦乐,但我不知道如何绕过它,不知道更多的背景...
答案 1 :(得分: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);
}
}
}
它简单明了,不需要或暴露对IPFormDataContext
和IPFormDataStrategy
类的某些结构的任何依赖。您拥有的唯一显式依赖项是具有FormProcessor
签名的委托。
与容器类似,您需要在某处执行注册:
FormsProcessing.RegisterProcessor("FormA", () => new FormAStrategy().DoWork);
FormsProcessing.RegisterProcessor("FormB", () => new FormBStrategy().DoWork);
或者,通过扫描约定的程序集(例如接口签名),可以很容易地添加某种形式的(基于约定的)自动注册。
答案 2 :(得分:1)
服务定位器为generally an anti-pattern,但重要的是要了解为什么这是一种反模式。原因通常与重构和类型安全有关。我认为确定你是否做错事的最佳方法是将问题减少到绝对最简单的要求,然后判断最简单的路径。
据我了解,您的要求是:
我的进一步问题是:
归结为它时,你必须使用某种标识符。正如@McGarnagle指出的那样,魔术字符串可能与代码不同步。您可以使用策略类的类型名称,但它具有相同的“可能不同步”问题。
如果表单类型不太可能改变,只需使用switch语句并自己实例化Strategy实例。简单性是可维护性的最佳设计模式。
如果他们可能会改变,服务定位器可以工作。如果您的服务定位器实现仅限于此代码,则维护不会那么糟糕。
在Ninject上,我不确定这个benchmark是否仍然有效,但Funq要快得多,我更喜欢语法。 (如果您决定使用DI容器)