这是服务定位器反模式和糟糕的解决方案吗?

时间:2013-12-12 13:46:49

标签: c# design-patterns dependency-injection

我已经实现了一个具有一些核心可重用类的解决方案,这些类可以使用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);    
    }

可能有ProductBProductC等版本,等等。

我的抽象工厂然后加载正确的命名实例,如下所示:

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

修改

工厂中加载的类由Product不可知的其他类使用 - 但是他们需要注入ProductAProductB行为。

    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

他是对的吗?

如果是这样,任何人都可以指出这个解决方案的缺点以及我如何改进它?

2 个答案:

答案 0 :(得分:0)

服务地点。这是一个问题,因为您已经引入了对服务定位器ObjectFactory的依赖,而不是您的IClaimPreparer类实际需​​要的接口AbstractClaimsFactory。这会使测试变得更难,因为你很难伪造IClaimPreparer的实现。它也会使你的类的意图变得模糊,因为类的依赖性是“不透明的”。

您需要研究使用Composition Root来解决反模式问题。看看Mark Seemann的工作,了解更多信息。

答案 1 :(得分:0)

他部分正确。给定一个好的DI容器,可以注册所有组件并解析对象树中的根对象... DI容器处理为根对象创建所有依赖项(递归)并为您创建整个对象树。然后你可以扔掉DI容器。这样做的好处是对DI容器的所有引用都局限于你的应用程序的entry-point

但是,你至少比曲线领先一步,因为你没有使用它们来解析对象的构造函数(或其他地方)中的依赖关系,而是在工厂中解析它们并将它们传递给通过构造函数注入需要它们的对象;)(这是我在代码中经常看到的东西,而且肯定是反模式。

这里有一些关于服务定位器以及它们如何成为反模式的更多信息: http://martinfowler.com/articles/injection.html http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/

这里有一点关于我暗示的configure-resolve-release类型模式: http://blog.ploeh.dk/2010/08/30/Dontcallthecontainer;itllcallyou/ http://kozmic.net/2010/06/20/how-i-use-inversion-of-control-containers/