我已经实现了一个解决方案,该解决方案具有一些核心可重用类,这些类可以使用StructureMap轻松注册和解析。然后,我有一个抽象工厂来在运行时加载其他系列的产品。
如果我有像这样的StructureMap注册表:
public ProductAClaimsRegistry()
{
var name = InstanceKeys.ProductA;
this.For<IClaimsDataAccess>().LifecycleIs(new UniquePerRequestLifecycle()).Use<ProductAClaimsDataAccess>().Named(name)
.Ctor<Func<DbConnection>>().Is(() => new SqlConnection(ConfigReader.ClaimsTrackingConnectionString));
this.For<IClaimPreparer>().LifecycleIs(new UniquePerRequestLifecycle()).Use<ProductAClaimPreparer>().Named(name);
this.For<IHistoricalClaimsReader>().LifecycleIs(new UniquePerRequestLifecycle()).Use<ProductAHistoricalClaimReader>().Named(name);
this.For<IProviderClaimReader>().LifecycleIs(new UniquePerRequestLifecycle()).Use<ProductAProviderClaimReader>().Named(name);
}
可能存在ProductB
、ProductC
等的版本
然后,我的抽象工厂加载正确命名的实例,如下所示:
public abstract class AbstractClaimsFactory
{
private IClaimsReader claimsReader;
private IClaimPreparer claimPreparer;
protected string InstanceKey { get; set; }
public virtual IClaimsReader CreateClaimReader()
{
return this.claimsReader;
}
public virtual IClaimPreparer CreateClaimPreparer()
{
return this.claimPreparer;
}
public void SetInstances()
{
this.CreateInstances();
var historicalReader = ObjectFactory.Container.GetInstance<IHistoricalClaimsReader>(this.InstanceKey);
var providerReader = ObjectFactory.Container.GetInstance<IProviderClaimReader>(this.InstanceKey);
this.claimsReader = new ClaimsReader(historicalReader, providerReader);
this.claimPreparer = ObjectFactory.Container.GetInstance<IClaimPreparer>(this.InstanceKey);
}
protected abstract void CreateInstances();
}
在运行时,有一个处理器类注入了一个具体的工厂,如下所示:
public void Process(AbstractClaimsFactory claimsFactory)
{
// core algorithm implemented
}
每个产品都有一个混凝土工厂:
public class ProductAClaimsFactory : AbstractClaimsFactory
{
public ProductAClaimsFactory()
{
SetInstances();
}
protected override void CreateInstances()
{
InstanceKey = InstanceKeys.ProductA;
}
}
编辑
工厂中加载的类由其他与产品无关的类使用,但它们需要注入ProductA
或ProductB
行为。
public ClaimsReader(IHistoricalClaimsReader historicalClaimsReader, IProviderClaimReader providerClaimsReader)
{
this.historicalClaimsReader = historicalClaimsReader;
this.providerClaimsReader = providerClaimsReader;
}
我不太确定这是否是text book abstract factory pattern
,我对StructureMap
和更先进的DI
是新手。
有了这个解决方案,我想我已经强制执行了一个核心算法,并在适当的时候重用了代码。
我还认为它是可扩展的,因为ProductN
可以在不更改现有代码的情况下轻松添加。
该解决方案还具有非常好的代码覆盖率,并且测试非常简单。
所以,底线是:我对这个解决方案很满意,但一位同事对它提出了质疑,特别是在使用ObjectFactory.Container.GetInstance<IClaimPreparer>(this.InstanceKey);
加载命名实例时,他说它看起来像Service Locator anti pattern
。
他说得对吗?
如果是的话,有人能指出这个解决方案的缺点吗?我该如何改进它?
此是服务位置。这是一个问题,因为您引入了对服务定位器ObjectFactory
的依赖,而不是AbstractClaimsFactory
类实际需要的接口IClaimPreparer
。这将使测试更加困难,因为您将很难伪造IClaimPreparer
的实现。它还模糊了类的意图,因为类的依赖关系是"不透明的"。
您需要研究组合根的使用来解决反模式。请查看Mark Seemann的作品,了解更多信息。
他部分正确。给定一个好的DI容器,就可以注册所有组件并解析对象树中的根对象。。。DI容器处理创建根对象的所有依赖项(递归),并为您创建整个对象树。然后您可以扔掉DI容器。这样做的好处是,所有对DI容器的引用都被限制在应用程序的入口点。
然而,您至少领先了一步,因为您没有使用依赖关系在对象的构造函数(或其他地方)中解析依赖关系,而是在工厂中解析了这些依赖关系,并通过构造函数注入将它们传递给需要它们的对象;)(这是我在工作的代码中经常看到的东西,这肯定是一种反模式
以下是关于服务定位器以及它们如何成为反模式的更多信息:http://martinfowler.com/articles/injection.htmlhttp://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/
以下是我所暗示的配置解析发布类型模式的更多信息:http://blog.ploeh.dk/2010/08/30/Dontcallthecontainer;它会打电话给你/http://kozmic.net/2010/06/20/how-i-use-inversion-of-control-containers/