我有一个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模式。这种方法的问题是与容器的耦合最紧密。不仅容器被直接引用,而且工厂依赖于支持子容器的容器的实现细节。
我总是喜欢给出一般的答案,而不是深入讨论特殊容器的特性。你说:
- 你需要消费者请求新的实例。
- 实例必须返回(可能被重用)。
使用工厂可能是最好的方法。第二个需求可以通过返回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);
}
}
这当然只是一个例子。你的设计可能会有所不同。根据您选择的容器,您可能会获得对工厂、对象池等的支持。我认为首先找到正确的设计是很重要的,下一步是看看你的容器是否能够帮助你做到这一点。