我有一个GUI应用程序。在其中,我允许用户从容器提供的算法列表中进行选择。每个算法将在另一个视图中作为后台任务启动。我需要支持此视图的多个实例,并支持同一算法的多个实例。该视图也将由容器提供。该算法也是有状态的。
所以我有一个案例,我需要创建我的视图和算法的实例,并在运行时将它们绑定在一起。我没有这些实例的静态绑定点,所以我不能使用正常的注入工具(构造函数或属性注入)。我不想拨打new
,我也不想像Service Locator那样使用容器。
我在Castle.Windsor中使用Typed Factory Facility解决了这个问题,但我不得不在我的应用程序中处理工厂。工厂设计也有点奇怪,因为当我完成工厂时,我必须将我的实例返回工厂。
我现在正在研究使用NInject,因为到目前为止,学习曲线和介绍文档要好得多,我想为我的团队提出一个容器供我使用。但对于这样的场景,我认为我必须编写自己的工厂并直接调用内核来解析新实例(嵌入在工厂中的Service Locator),以及在我的注册码中添加工厂方法。
是否有一种通用的方法来解决这个问题,或者这只是一个依赖注入本身不能解决的问题?
澄清:
我在评论中说我想要一个Ninject的具体答案,我已经得到了。非常感谢:)在现实生活中,我可能只是使用已提出的实用解决方案。
但是我把我的基础作为具体问题来解决我的问题。我希望在我的标题中对这个问题有一个更纯粹的基本答案。
是否有纯DI技术允许用户在运行时触发新的组件实例?或者所有这些实现都将容器用作服务定位器,或者需要对容器的特定“怪癖”(例如内置工厂支持,ala Castle.Windsor或即将发布的Ninject工厂特性),而不是而不只是利用“纯”DI的方面?
我只是从Java世界中听到过这个词,我对它的含义并不太了解 - 请原谅我:)我正在寻找某种“注射”吗?
答案 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 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)
我总是喜欢展示一般的答案,而不是特别容器的功能。你说以下几点:
使用工厂可能是最好的方法。第二个要求可以通过返回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);
}
}
这当然只是一个例子。你的设计可能会有所不同。根据您选择的容器,您可能会获得对工厂,对象池以及不支持的容器的支持。我认为首先找到正确的设计很重要,下一步是看看你的容器是否能够帮助你。