【问题标题】:Circular Dependency in Dependency Injection via Constructors通过构造函数进行依赖注入中的循环依赖
【发布时间】:2013-07-30 13:11:19
【问题描述】:

我在一个项目中使用 Netty (4.0.4.Final),但我一直遇到需要排除的循环依赖。这个问题主要涉及分解循环依赖的概念,但我会为熟悉的人使用一些 Netty 术语。不过,由于我的问题实际上与 Netty 无关,所以我决定不标记它。

下面,我发布了我的代码,省略了我认为不相关的部分。

情况

我有一个MyServer 类,它将ChannelInboundHandlerAdapter 添加到Bootstrap

public class MyServer extends AbstractMyServer {
    private Integer someInteger; //Using Integer just for example's sake.

    public MyServer(MyServerInitializer initializer) {
        //...
        bootstrap.handler(initializer);
        //...
    }

    public void updateInteger(Integer value) {
        someInteger = value;
        //Send an update packet to another server.
    }
}

MyServerInitializer 需要在ChannelPipeline 中添加一个ChannelInboundHandlerAdapter

public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
    private ChannelInboundHandlerAdapter handler;

    public MyServerInitializer(ChannelInboundHandlerAdapter handler) {
        this.handler = handler;
    }

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast(
                new ObjectEncoder(),
                new ObjectDecoder(),
                handler);
    }
}

我还有一个MyServerHandler,在我所说的情况下,它是MyServerInitializer 的构造函数参数:

public class MyServerHandler extends ChannelInboundHandlerAdapter {
    private MyServer server;

    public MyServerHandler(MyServer server) {
        this.server = server;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Integer obj = (Integer) msg; //Remember just using Integer for example. Think of it as an Object rather than an Integer.
        server.updateInteger(obj);
    }
}

因此,循环依赖在初始化期间变得明显:

public static void main(String[] args) {
    //I can't set a reference to MyServer instance here because it hasn't been created yet. I want to avoid the circular dependency here.
    MyServerHandler handler = new MyServerHandler(...);
    MyServerInitializer initializer = new MyServerInitializer(handler);
    MyServer server = new MyServer(initializer);
}

可能的解决方案

重构为main()

我可以从MyServer 中提取Integer someInteger 的创建,在main() 函数的范围内创建它,然后将它的引用注入MyServerHandlerMyServer。这当然会给MyServerHandler 直接修改它的能力,而不必经过MyServer。缺点是它现在被声明在main() 的范围内。我不想对每个可能本质上需要由Handler 类修改的类成员执行此操作。

创建一个MyServerFactory

我读到的其中一个概念是将构造与使用分开。这很有意义,所以我试了一下下面的工厂模式实现。

public class MyServerFactory implements AbstractFactory<MyServer> {
    public MyServer create() {
        Integer someInteger = createInteger();
        MyServerHandler handler = createHandler(someInteger);
        MyServerInitializer initializer = createInitializer(handler);
        return new MyServer(initializer);
    }

    /* creator methods for the different components above. */
}

但是,我似乎只是将代码从 main() 移到了这个 Factory 类。

问题

  1. 如果我想将不同的Handler 注入MyServerInitializer 会发生什么- 也许这个新的Handler 不接受Integer 作为参数。我是否必须为这种情况创建一个新的Factory
  2. 拥有一个可能只会创建一个 MyServer 实例的 Factory 有意义吗?
  3. 是否有其他选项可用于排除此循环引用?

粗体字是我在 StackOverflow 上提出这个问题的主要焦点。我觉得我必须在这里忽略一些更简单或更优雅的东西。我希望你们中一些更有经验的用户可以提供一些见解。如果需要更多信息,请告诉我。

参考资料

【问题讨论】:

  • 我认为几乎肯定应该有一种更清洁的方式。考虑到我对框架的不熟悉和不太有用的类名,很难准确地说出中断应该发生在哪里,但目前所有的类都太了解其他类了。某个地方应该有应用程序本身,并且这些片段应该在应用程序中注册自己(因此允许其他片段也可以注册而无需修改现有类)......
  • MyServer 或 MyServerInitializer 都应该有一个公共方法,该方法指向适当的handler,然后作为注册者的类应该在实例化时调用该方法。由于MyServerHandler 知道它的容器,我建议尝试让它在该容器中注册,而不是将相关的知识分成两个位置。
  • 作为一个快速跟进......你已经有一个初始化器,其目的是构造/接线,所以我当然会质疑是否需要添加一个工厂来将相同的部分粘合在一起。
  • 我开始将中介者模式视为解决我的问题的潜在手段。我的计划是将MyServer 放在MyServerDirector 上,IntegerIntegerUpdater 实例将处理Integer 的更新。请记住,Integer 只是此示例中更复杂对象的占位符。似乎Integer 甚至不应该出现在MyServer 上。我相信在MyServer 上是违反SRP 的。 MyServer 的 SRP 是充当两点之间的链接——不维护对象的更新。你怎么看?
  • 我当然同意 MyServer 除了充当某种形式的调度程序/反应器之外不应该做任何事情,并且您最好使用某种形式的服务或类似的东西来维护对象,但它是如果不花时间处理整个画面,很难说更多。我现在还提醒您,您似乎也有一个 Bootstrap 对象,这将是处理构造/布线的第二个现有类。我建议在可能过度架构或捕获模式炎之前更仔细地查看已经存在的部分之间的关​​系

标签: java factory circular-dependency code-cleanup


【解决方案1】:

免责声明:我对 Netty 了解不多,这些只是阅读您的代码的一些想法:

我认为MyServerInitializer 没有任何问题。 MyServerInitializerMyServerHandlerMyServer 没有任何依赖关系。没关系。如果MyServerInitializer 的构造函数需要MyServerHandler 而不是ChannelInboundHandlerAdapter,情况会更糟。

如果可能,您应该将MyServer 的构造函数参数从MyServerInitializer 更改为ChannelInitializer&lt;SocketChannel&gt;

问题在于MyServerHandler 依赖于MyServer,而MyServer 间接运行时依赖于MyServerHandler。我会尝试摆脱MyServerHandler 中的MyServer 依赖项。为此,您可以:

  • updateInteger() 方法从MyServer 移到另一个类中,我们称之为IntegerUpdaterMyServerHandler 应该使用 IntegerUpdater 而不是 MyServerIntegerUpdater 应该不依赖于 MyServer。使用这种方式,您将不会有任何循环依赖。

  • MyServerHandlerMyServer 之间添加一个抽象。例如:

    public interface IntegerMessageReceiver {
      void handleMessage(Integer i);
    }
    

-

    public class MyServerHandler extends ChannelInboundHandlerAdapter {
      private List<IntegerMessageReceiver> integerMessageReceivers;

      public void addIntegerMessageReceiver(IntegerMessageReceiver imr) {
        integerMessageReceivers.add(imr);
      }

      @Override
      public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Integer obj = (Integer) msg; 
        for (IntegerMessageReceiver imr : integerMessageReceivers) { 
          imr.handleMessage(obj);
        }
      }
    }

-

    public class MyServer extends AbstractMyServer implements IntegerMessageReceiver {
      public void handleMessage(Integer i) {
        ...
      }
      ...
    }

初始化:

MyServerHandler handler = new MyServerHandler();
MyServerInitializer initializer = new MyServerInitializer(handler);
MyServer server = new MyServer(initializer);
handler.addIntegerMessageReceiver(server);

使用这种方法你仍然会有循环运行时依赖,但至少你摆脱了MyServerMyServerHandler 中的直接编译时依赖。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-02
    • 2017-01-09
    • 1970-01-01
    • 2019-04-20
    • 2018-06-17
    相关资源
    最近更新 更多