如何在不使用服务定位器模式的情况下使用依赖注入的工厂



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

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

我在Castle解决了这个问题。但是我必须处理我的应用程序中的所有工厂。工厂设计也有点奇怪,因为当我完成它们时,我必须将我的实例返回到工厂。

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

是否有一个通用的方法来解决这个问题,或者这只是一个问题,依赖注入不是设计来解决自己?


澄清:

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

但是我把我的基础当成了一个具体的问题,把我的问题搞砸了。我希望对我标题中的问题有一个更纯粹的基本答案。

是否有一种纯di技术允许用户在运行时触发组件的新实例?或者所有这样的实现都使用容器作为服务定位器,或者要求容器具有特定的"特性"(例如,内置工厂支持,或者Castle)。Windsor或即将发布的Ninject factory特性),而不是只利用"纯"DI的某些方面?

我只从Java世界听说过这个词,我不太清楚它是什么意思-所以请原谅我:)我正在寻找的是某种"排斥"吗?

最好创建一个像这样的工厂接口

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

我非常感谢大家的回答。他们会帮我解决这个问题。我很可能会接受Remo的答案,因为它符合我目前实际面临的问题。

为了我的理解,我还想得到关于我想出的这个更广泛的答案的反馈。


我不确定依赖注入是否通过构造函数、属性或方法注入直接支持我刚才谈到的机制。在这一点上,这些是我认为"纯"的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);
}

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

确保您的组件是无状态的,并允许它们被池化。容器将负责为您分配或池化对象。这或多或少是一个具有奇特实现的托管工厂,您必须从其中分配和释放。

伪代码(我以前没有使用过基于容器的对象池):
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 -不确定我是否正确地识别了模式,或者我是否只是对它有一个奇怪的用法)

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

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

这并不适用于ui。视图类通常需要直接创建,我们不希望我们的"轻量级"是一个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);
    }
}

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

我总是喜欢给出一般的答案,而不是深入讨论特殊容器的特性。你说:

  1. 你需要消费者请求新的实例。
  2. 实例必须返回(可能被重用)。

使用工厂可能是最好的方法。第二个需求可以通过返回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);
    }
}

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

相关内容

最新更新