如何使用具有依赖注入的工厂而不使用服务定位器模式

时间:2011-11-17 11:56:23

标签: .net dependency-injection ninject factory-pattern

我有一个GUI应用程序。在其中,我允许用户从容器提供的算法列表中进行选择。每个算法将在另一个视图中作为后台任务启动。我需要支持此视图的多个实例,并支持同一算法的多个实例。该视图也将由容器提供。该算法也是有状态的。

所以我有一个案例,我需要创建我的视图和算法的实例,并在运行时将它们绑定在一起。我没有这些实例的静态绑定点,所以我不能使用正常的注入工具(构造函数或属性注入)。我不想拨打new,我也不想像Service Locator那样使用容器。

我在Castle.Windsor中使用Typed Factory Facility解决了这个问题,但我不得不在我的应用程序中处理工厂。工厂设计也有点奇怪,因为当我完成工厂时,我必须将我的实例返回工厂。

我现在正在研究使用NInject,因为到目前为止,学习曲线和介绍文档要好得多,我想为我的团队提出一个容器供我使用。但对于这样的场景,我认为我必须编写自己的工厂并直接调用内核来解析新实例(嵌入在工厂中的Service Locator),以及在我的注册码中添加工厂方法。

是否有一种通用的方法来解决这个问题,或者这只是一个依赖注入本身不能解决的问题?


澄清:

我在评论中说我想要一个Ninject的具体答案,我已经得到了。非常感谢:)在现实生活中,我可能只是使用已提出的实用解决方案。

但是我把我的基础作为具体问题来解决我的问题。我希望在我的标题中对这个问题有一个更纯粹的基本答案。

是否有纯DI技术允许用户在运行时触发新的组件实例?或者所有这些实现都将容器用作服务定位器,或者需要对容器的特定“怪癖”(例如内置工厂支持,ala Castle.Windsor或即将发布的Ninject工厂特性),而不是而不只是利用“纯”DI的方面?

我只是从Java世界中听到过这个词,我对它的含义并不太了解 - 请原谅我:)我正在寻找某种“注射”吗?

3 个答案:

答案 0 :(得分:8)

最好创建像这样的工厂界面

public interface IFooFactory
{
    IFoo CreateFoo(int someParameter);
}

对于Ninject 2.3,请参阅https://github.com/ninject/ninject.extensions.factory并通过添加以下配置由Ninject实现。

Bind<IFooFactory>().AsFactory();

对于2.2自己实施。此实现是容器配置的一部分,而不是实现的一部分。

public class FooFactory: IFooFactory
{
    private IKernel kernel;
    public FooFactory(IKernel kernel)
    {
        this.kernel = kernel;
    }

    public ISession CreateFoo(int someParameter)
    {
        return this.kernel.Get<IFoo>(
            new ConstructorArgument("someParameter", someParameter));
    }
}

答案 1 :(得分:3)

我非常感谢其他人的答案。他们会帮助我解决这个问题。我很可能接受雷莫的回答,因为它符合我实际面临的当前问题。

根据我的理解,我也希望得到关于我提出的更广泛答案的反馈。


我不确定依赖注入通过构造函数,属性或方法注入直接支持我所讨论的机制。这就是我在这一点上所认为的“纯粹”DI - 虽然我愿意动摇。

我认为注入依赖关系意味着一个相对静态的对象图。它可以从配置文件加载,也可以通过程序生成,但它不能直接容纳不可知的运行时状态,就像用户反复请求新实例一样。

然而,在考虑了其中一些替代方案之后,我开始认为有一些支持纯度的解决方案,也许我所描述的纯度并不像我想象的那么重要。一些较不“纯粹”的选项仍然以大多数干净的方式与大多数容器一起使用,并且似乎很容易添加对容器的支持以在剩下的路上清理它们。

以下是我到目前为止所考虑的解决方法(其中一些已被提及)。

在自定义工厂中引用容器,然后洗手:

您可以随意实施您的组件。您可以使用您想要的任何容器(只要它支持瞬态实例)。您只需要接受这样一个事实:您将在您的代码中注入工厂,并且这些工厂将直接从容器中解析。当你务实时,谁需要纯洁?

示例代码:

public class ComponentFactory // Might inherit from an interface...
{
    private readonly IContainer container;

    public ComponentFactory(IContainer container)
    {
        this.container = container;
    }

    public IComponent Create(IOtherComponent otherComponent)
    {
        return container.Get<IComponent>(otherComponent);
    }
}

使用特定于容器的工厂扩展名:

您可以随意实施您的组件。但是您的容器必须直接支持将工厂注入代码,并自动实现这些工厂,这样他们就不必具备容器的任何特定知识。

示例代码:

// Black magic - the container implemented it for us!
// But the container basically implemented our code from the previous example...
public interface IComponentFactory
{
    public IComponent Create(IOtherComponent otherComponent);
}

使用特定于容器的对象池:

确保您的组件是无状态的,并允许它们合并。容器将负责为您分配或汇集对象。这或多或少是一个带有花哨实现的托管工厂,您必须分配和解除分配。

伪代码(之前我没有使用过基于conatiner的对象池):

public class SomeUI
{
    private readonly IComponentPool componentPool;

    public OtherComponent(IComponentPool componentPool)
    {
        this.componentPool = componentPool;
    }

    public void DoSomethingWhenButtonPushed()
    {
        var component = componentPool.Get();
        component.DoSomething();
        componentPool.Release(component);
    }
}

此伪代码的优点是您不必为工厂定义接口。缺点是您必须依赖池接口,因此您的容器上有卷须。此外,我没有向Get方法传递任何内容。这可能是有道理的,因为对象必须支持实例重用。

如果实际池不能像这样工作,它们看起来可能与上面的“特定于容器的工厂扩展”示例相同,只是它们总是需要Release方法和Create方法。方法

使用Flyweight模式:

Flyweight Pattern - 不确定我是否正确识别了模式,或者我是否只是奇怪地使用了它?)

注入一个无状态组件,它充当对象或“重量级”组件的行为。使用传递给行为组件的flyweight状态对象支持组件的单独“实例”,或者让它们包装行为组件。

这将极大地影响组件的体系结构。您的实现必须是无状态的,并且您的状态对象必须以适用于所有可能的组件实现的方式设计。但它完全支持“纯”注入模型(仅注入构造函数,属性,方法)。

但这对UI来说效果不佳。通常需要直接创建视图类,我们不希望我们的“flyweight”成为UI类......

public class ComponentState
{
    // Hopefully can be less generic than this...
    public Dictionary<string, object> Data { get; set; }
}

public interface IComponent
{
    int DoSomething(ComponentState state);
}

public SomeUI
{
    private readonly IComponent component;

    public OtherComponent(IComponent component)
    {
        this.component = component;
    }

    public void DoSomethingWhenButtonPushed()
    {
        var state = new ComponentState();
        component.DoSomething(state);
    }
}

为用户请求的每个新实例使用子容器:

从单个根创建对象图时,容器的效果最佳。不要试图与之斗争,而是与之合作。当用户单击按钮以创建算法的新实例时,为这些对象创建新的子容器并调用全局配置代码,但需要完成。然后将子容器附加到父容器。

这意味着产卵代码需要在某个级别了解容器。也许将它包装在工厂是最好的方法。

public class SubComponentFactory // Might inherit from an interface...
{
    private readonly IContainer container;

    public ComponentFactory(IContainer container)
    {
        this.container = container;
    }

    public IComponent Create(IOtherComponent otherComponent)
    {
        // Todo: Figure out any lifecycle issues with this.
        // I assume the child containers get disposed with the parent container...

        var childContainer = container.CreateChildContainer();
        childContainer.Configure(new SubComponentConfiguration());

        return childContainer.Get<SubComponent>(otherComponent);
    }
}

有点像我们开始的地方。但是我们有一个新的对象图根,所以我不确定我是否可以使用服务定位器模式来调用它。这种方法的问题是与容器最紧密耦合。不仅直接引用容器,而且工厂依赖于支持子容器的容器的实现细节。

答案 2 :(得分:2)

我总是喜欢展示一般的答案,而不是特别容器的功能。你说以下几点:

  1. 您需要消费者要求新的实例。
  2. 必须返回实例(可能需要重复使用)。
  3. 使用工厂可能是最好的方法。第二个要求可以通过返回IDisposable个对象来解决。然后,您可以编写如下代码:

    using (var algorithm = this.algoFactory.CreateNew(type))
    {
        // use algorithm.
    }
    

    有多种方法可以执行此操作,但您可以让算法接口实现IDisposable

    public interface IAlgorithm : IDisposable
    {
    }
    

    您可以返回一个装饰器,而不是返回真实算法,该装饰器允许将该实例返回到池中:

    public sealed class PooledAlgorithmDecorator : IAlgorithm
    {
        private readonly IAlgorithmPool pool;
        private IAlgorithm real;
    
        public PooledAlgorithmDecorator(IAlgorithm real,
            IAlgorithmPool pool)
        {
            this.real = real;
            this.pool = pool;
        }
    
        public void Dispose()
        {
            if (this.real != null)
            {
                this.Pool.ReturnToPool(this.real);
                this.real = null;
            }
        }
    }
    

    现在你的工厂可以用装饰器包装真实算法并将其返回给消费者:

    public class PooledAlgorithmFactory : IAlgorithmFactory
    {
        private readonly IAlgorithmPool pool;
    
        public PooledAlgorithmFactory(IAlgorithmPool pool)
        {
            this.pool = pool;
        }
    
        public IAlgorithm CreateNew(string type)
        {
            var instance = this.pool.GetInstanceOrCreateNew(type);
    
            return new PooledAlgorithmDecorator(instance, this.pool);
        }
    }
    

    这当然只是一个例子。你的设计可能会有所不同。根据您选择的容器,您可能会获得对工厂,对象池以及不支持的容器的支持。我认为首先找到正确的设计很重要,下一步是看看你的容器是否能够帮助你。