我想知道如何在使用DI框架时正确使用抽象工厂,并且该工厂中的一个参数是应该由DI框架处理的依赖项。
我不确定是否要让我的抽象工厂完全省略参数然后使用我的DI容器来连接它或者是否应该将依赖项传递给对象。
例如,我有一个TcpServer,它使用Session.Factory来创建套接字。 Session对象实际上在其构造函数中使用了Processor。我应该将处理器传递给TcpServer然后将它传递给Session.Factory还是让我的DI容器进行接线?
如果我要让DI容器进行接线,它将如下所示:
class Session : ISession
{
public delegate ISession Factory(string name);
...
public Session(string name, Processor processor)
{
...
}
}
class TcpServer : ITcpServer
{
private readonly Session.Factory _sessionFactory;
public TcpServer(Session.Factory sessionFactory)
{
this._sessionFactory = socketFactory;
}
...
public void OnConnectionReceived()
{
...
var session= _sessionFactory(ip.LocalEndPoint());
...
}
}
然后使用像Ninject这样的DI容器,我可以在配置容器时执行此操作:
Bind<Session.Factory>().ToMethod(c =>
{
var processor = Kernel.Get<Processor>();
return (name) => new Session(name, processor);
}).InSingletonScope();
我使用这种方法的主要问题是它假设创建Session.Factory的人知道处理器。在我的情况下,由于我使用的是DI容器,这实际上非常方便,但是让工厂拥有自己的依赖项似乎很奇怪。我一直想象一个工厂真的没有任何成员。
如果我通过
传递依赖关系class Session : ISession
{
public delegate ISession Factory(string name, Processor processor);
...
public Session(string name, Processor processor)
{
...
}
}
class TcpServer : ITcpServer
{
private readonly Session.Factory _sessionFactory;
private readonly Processor _processor;
public TcpServer(Session.Factory sessionFactory, Processor processor)
{
this._processor = processor;
}
...
public void OnConnectionReceived()
{
...
var session = _sessionFactory(ip.LocalEndPoint(), _processor);
...
}
}
我对第二种方法有两个问题:
您认为最好的方法是什么?我也对新想法持开放态度。
谢谢!
答案 0 :(得分:4)
许多容器以一种或另一种方式支持工厂,这就是你应该去的方式。
E.g。以您的示例为例,定义一个ISessionFactory接口
public interface ISessionFactory
{
ISession CreateSession(string name);
}
对于Ninject 2.3,请参阅https://github.com/ninject/ninject.extensions.factory并让它由Ninject实施
Bind<ISessionFactory>().AsFactory();
对于2.2自己实施
public class SessionFactory : ISessionFactory
{
private IKernel kernel;
public SessionFactory(IKernel kernel)
{
this.kernel = kernel;
}
public ISession CreateSession(string name)
{
return this.kernel.Get<ISession>(new ConstructorArgument("name", name));
}
}
答案 1 :(得分:1)
我用于抽象工厂模式的模式与您的模式略有不同。我在通用单例上使用类似setter注入的东西,但在更直观的界面中包装可配置委托“property”。
我宁愿不必单独注册每个实现,所以我更喜欢使用一些可以在应用程序启动时测试的约定。我不确定用于自动调整自定义约定的Ninject语法,但逻辑将归结为扫描相关程序集的引用类型T,它们具有AbstractFactory<T>
类型的静态只读字段,然后调用{{1}使用反射在静态成员上。
通用抽象工厂单例的一个示例以及如何在Configure(Func<T>)
上声明它。
Session
如果需要将参数传递给工厂方法,则可以更改类,例如:
public class Session {
public static readonly AbstractFactory<Session> Factory = AbstractFactory<Session>.GetInstance();
}
public sealed class AbstractFactory<T>
where T: class{
static AbstractFactory(){
Bolt = new object();
}
private static readonly object Bolt;
private static AbstractFactory<T> Instance;
public static AbstractFactory<T> GetInstance(){
if(Instance == null){
lock(Bolt){
if(Instance == null)
Instance = new AbstractFactory<T>();
}
}
return Instance;
}
private AbstractFactory(){}
private Func<T> m_FactoryMethod;
public void Configure(Func<T> factoryMethod){
m_FactoryMethod = factoryMethod;
}
public T Create() {
if(m_FactoryMethod == null) {
throw new NotImplementedException();
}
return m_FactoryMethod.Invoke();
}
}
您的 public sealed class AbstractFactory<TDataContract,T>
where T: class{
static AbstractFactory(){
Bolt = new object();
}
private static readonly object Bolt;
private static AbstractFactory<TDataContract,T> Instance;
public static AbstractFactory<TDataContract,T> GetInstance(){
if(Instance == null){
lock(Bolt){
if(Instance == null)
Instance = new AbstractFactory<T>();
}
}
return Instance;
}
private AbstractFactory(){}
private Func<TDataContract,T> m_FactoryMethod;
public void Configure(Func<TDataContract,T> factoryMethod){
m_FactoryMethod = factoryMethod;
}
public T Create(TDataContract data) {
if(m_FactoryMethod == null) {
throw new NotImplementedException();
}
return m_FactoryMethod.Invoke(data);
}
}
,SessionData
和Session
可能看起来像
TcpServer
配置DI容器时,可以设置工厂,例如:
public class SessionData{
public DateTime Start { get; set; }
public string IpAddress { get; set; }
}
public class Session {
public static readonly AbstractFactory<SessionData,Session> Factory = AbstractFactory<Session>.GetInstance();
private readonly string _ip;
private readonly DateTime _start;
public Session(SessionData data) {
_ip = data.IpAddress;
_start = DateTime.Now;
}
public event EventHandler<RequestReceivedEventEventArgs> RequestAdded;
}
public class RequestReceivedEventArgs: EventArgs {
public SessionRequest Request { get; set; }
}
public class TcpServer : ITcpServer
{
private readonly Processor _processor;
public TcpServer(Processor processor)
{
this._processor = processor;
}
public void OnConnectionReceived()
{
var sessionData = new SessionData {
IpAddress = ip.LocalEndPoint(),
Start = DateTime.Now
};
var session = Session.Factory.Create(sessionData);
//...do other stuff
}
public void ServeResponse(SessionRequest request){
_processor.Process(request);
}
}