Java Refactor导致了循环引用

时间:2010-11-08 14:05:54

标签: java refactoring dependency-injection

我在桌面应用程序中有这种代码

这只是一个包含按钮等的JPanel。

class ApplicationPanel {

   private Listener listener;

   public ApplicationPanel(){
      this.listener = new Listener(this);
   }
}

这会将事件添加到上面JPanel中的控件。

class Listener {

   private ApplicationPanel panel;

   public Listener(ApplicationPanel panel){
      this.panel = panel;
   }
}

来电代码就像这样

public void main(String[] args){
   ApplicationPanel panel = new ApplicationPanel();
}

如果我尝试应用依赖注入 和工厂(后来改为Guice)

class ApplicationPanel {

   private Listener listener;

   public ApplicationPanel(){
      this(new Listener());
   }

   public ApplicationPanel(Listener listener){
      this.listener = listener;
   }
}

class Listener {

   private ApplicationPanel panel;

   public Listener(){
      this(new ApplicationPanel());
   }

   public Listener(ApplicationPanel panel){
      this.panel = panel;
   }
}

来电代码就是这样的 如您所见,此代码中存在循环依赖关系 什么是解决这个问题的最佳方法?

public void main(String[] args){
   Listener listener = new Listener(panel);
   ApplicationPanel panel = new ApplicationPanel(listener);

}

3 个答案:

答案 0 :(得分:3)

查看有关该主题的this blog post;鉴于您在guice模块中提供的绑定,guice足够聪明,可以检测循环引用,然后使用临时代理,以便解决注入问题。

答案 1 :(得分:2)

一般解决方案是:使用依赖注入时,始终使用默认构造函数和setter。

这样,您可以创建所有对象(此阶段没有依赖项),立即调用所有setter,然后开始使用对象。

答案 2 :(得分:2)

问题是你无法使用假ApplicationPanelListener进行单元测试,因为Listener接受ApplicationPanel(如果您使用Listener的默认构造函数监听器将有一个引用真实ApplicationPanel的字段。你可以使用带有模拟框架的模拟Listener,但在这种情况下,我认为循环依赖可能表示代码气味。

这种循环依赖的真正问题是ListenerApplicationPanel完成构建之前收到对ApplicationPanel的引用。这可能会导致线程安全问题。即使代码路径不是多线程的,Listener也可能会在ApplicationPanel初始化侦听器列表之前调用导致事件发送的ApplicationPanel

而是在收到事件时将数据传递给侦听器:

public interface Listener {

  void onApplicationChanged(ApplicationPanel panel){
  }
}

将侦听器添加到发送事件的对象的更传统的方法是进行方法调用:

public class ApplicationPanel {
  private List<Listener> listeners = new CopyOnWriteArrayList<Listener>();

  public void addListener(Listener listener) {
    listeners.add(listener);
  }
}

如果您希望发送事件的类使用构造函数指定侦听器,并且您想要使用Guice,请查看MultibinderApplicationPanel看起来像这样:

public class ApplicationPanel {
  private Set<Listener> listeners;

  @Inject
  public ApplicationPanel(Set<Listener> listeners) {
    listeners = new HashSet<Listener>(listeners);
  }
}