Java 在IoC容器中将依赖项设置为NULL并在运行时提供依赖项是否是一种不好的做法?

Java 在IoC容器中将依赖项设置为NULL并在运行时提供依赖项是否是一种不好的做法?,java,design-patterns,dependency-injection,abstract-factory,Java,Design Patterns,Dependency Injection,Abstract Factory,我有一个SocketManager类,它包含一个Socket和其他字段。在使用DI框架合成对象图的过程中,除了套接字之外的所有字段都可以被注入。我的想法是将Socket保留为空,并在运行时设置它,从而预先构建整个对象图。这将允许我在代码中的某个点完成SocketManager实例化,并在整个程序中使用该实例(因为它已经通过DI框架设置为依赖项)?这是“注入”运行时依赖项的标准方法还是不好的做法? 抽象工厂似乎是个坏主意,原因有两个:A)它每次都创建一个不同的对象b)它需要在我想要创建对象的每个地

我有一个
SocketManager
类,它包含一个
Socket
和其他字段。在使用DI框架合成对象图的过程中,除了
套接字
之外的所有字段都可以被注入。我的想法是将
Socket
保留为空,并在运行时设置它,从而预先构建整个对象图。这将允许我在代码中的某个点完成
SocketManager
实例化,并在整个程序中使用该实例(因为它已经通过DI框架设置为依赖项)?这是“注入”运行时依赖项的标准方法还是不好的做法?
抽象工厂似乎是个坏主意,原因有两个:A)它每次都创建一个不同的对象b)它需要在我想要创建对象的每个地方使用运行时参数

让我举例说明我的问题:

SocketManager类:

public class SocketManager {
    //i'll only receive the socket at runtime
    Socket socket; 
    //this object is available at compile-time and can be injected through the DI container
    InjectableObject obj;
}
在我的代码[CodePosition1]的某个地方,我将收到如下套接字:

public class SocketCreator{
    SocketManager socketManager; //will be injected through DI container at startup
    Socket socket = this.serverSocket.accept();
    // at this point the socket manager is fully initialized
    socketManager.setSocket(socket); 
}
在许多其他地方[CodePosition2],我现在可以使用SocketManager依赖项

public class RandomClass {
    //injected at compile-time through DI container, but only usable after [CodePosition1]
    // was executed
    SocketManager socketManager; 
    ...
        socketManager.getSocket().doSth()
    ...
}

问题是,
SocketManager
在运行时[CodePosition1]之前没有完全初始化,所以除了在SocketManager上使用init()或setter来“完成”SocketManager的初始化之外,我不知道其他方法。然而,这是一个漏洞百出的抽象,正如本文所解释的:

最好从一开始就编写完整的对象图。应该防止注入
null
值,因为这会使消费类复杂化

然而,在您的例子中,
Socket
似乎不是一个“真正的”组件,而是运行时数据。如上所述,您应该防止在构建期间将运行时数据注入到对象图中

那篇文章给出了两种解决这个问题的方法,但是还有更多的方法。然而,正如您已经提到的,抽象工厂通常不是一个好的解决方案,并且在更一般的意义上描述了抽象工厂的问题所在。第段甚至从DI的角度详细介绍了抽象工厂的问题

博客文章中给出的解决方案是使用“上下文”抽象。例如,在您的例子中,一个
SocketContext
接口,它允许您在调用消费者的方法之后,以及在构建消费者的对象图之后,由消费者获取
Socket
运行时值。例如:

public interface SocketContext
{
    Socket get_CurrentSocket();
}
public class SocketManagerLazyProxy : SocketManager
{
    private SocketManager mananger;

    public void DoSomething()
    {
        if (manager == null) manager = new RealSocketManager(new Socket());
        manager.DoSomething();
    }   
}
另一个选项是使用一个代理类,该类隐藏real
Socket
或real
SocketManager
(取决于可以放置代理的级别)。这允许使用者不知道某些运行时数据需要在后台初始化,并且在第一次调用后可能会延迟初始化。例如:

public interface SocketContext
{
    Socket get_CurrentSocket();
}
public class SocketManagerLazyProxy : SocketManager
{
    private SocketManager mananger;

    public void DoSomething()
    {
        if (manager == null) manager = new RealSocketManager(new Socket());
        manager.DoSomething();
    }   
}
另一个选项是在构建对象图后使用属性注入来设置
套接字
值。这允许您更早地构建对象图,并在请求传入时设置运行时值,方法是在请求传入时设置该值:

void HandleRequest(RequestData data)
{
    SocketManager manager = GetSocketManagerForThisRequest();
    manager.Socket = new Socket();
    Handler handler = GetHandler(data.Name);
    handler.Handle(data);
}

你能再解释一下吗?也许可以添加一些代码。。。您打算使用什么DI框架?@FabioBohnenberger我添加了一个示例来说明我的观点;-)如果客户端代码在套接字可用之前与
SocketManager
交互,会发生什么情况?是否有多个并发套接字?插座能再次消失吗?不,只有一个SocketManager和一个插座,插座不能消失。我在没有任何DI框架的情况下启动了这个项目,因此,
Socket
总是在程序的早期创建的。然后我通过构造函数将
SocketManager
传递到整个堆栈的任何需要的地方,这显然是不好的。因此,在实际操作中,
RandomClass
使用SocketManager时,它将被完全初始化。如果我可以问的话,您的问题与我的演示问题有什么关系?我得出的结论是,在运行时设置的变量在某一点上肯定是空的(无论您是否将其包装到容器中)。这没有意义。因此,在消费者调用该变量之前,程序员的责任是确保首先设置该变量。在使用DI框架时,这是您必须接受的权衡,因为一个普通的构造函数链将迫使您指定一个时间链,这保证了一个“之前发生”的关系。感谢Steven,我添加了一个示例来说明我的观点。让我们来看一下“SocketContext”解决方案。据我所知,SocketContext与SocketManager类似,因此同样的问题仍然存在:如何在运行时设置
SocketContext
?您需要将
Socket
保留为NULL,并在运行时完成SocketContext的初始化。只有这样,您才能使用
get\u CurrentSocket()
。在没有init()或setter的情况下,如何解决这个问题,如示例中所示?在我看来,依赖项的方向是错误的。如果让
SocketManager
依赖于
SocketCreator
,它可以在第一次请求
Socket
时调用创建者。如果
SocketManager
具有
SocketCreator
依赖性,那么我可以通过
SocketCreator
使用套接字,这就是你的意思吗?但是,当我通过SocketManager调用SocketCreator时,如何确保它已实际设置?这不是某种时间耦合问题吗?此外,在对象图初始化期间,
Socket
内部的
SocketCreator
需要成为类成员并在编译时为NULL。为什么必须设置creator?创建不是创建者的工作吗?嗯,我想我们应该确保在访问套接字之前设置好它,否则
SocketManager
将处于