如何避免使用观察者模式的循环引用?

时间:2017-04-04 06:58:25

标签: java oop observer-pattern circular-reference

我有这样的情况:

public class A
{
    private ArrayList<Listener> listeners = new ArrayList<Listener>();

    public A()
    {
        // stub
    }

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

    public void removeListener(Listener listener)
    {
        this.listeners.remove(listener);
    }

    public interface Listener
    {
        // stub
    }
}

public class B implements A.Listener
{
    private A instanceOfA;

    public B()
    {
        this.instanceOfA = new A();
        this.instanceOfA.addListener(this);
    }
}

我相信B永远不会被破坏,因为A保持对它的引用,而A永远不会被销毁,因为B保留了对它的引用。

这似乎是一种常见的设计模式,但是没有人解释如何避免这种循环引用?

3 个答案:

答案 0 :(得分:1)

在实践中,这已得到解决,因此观察者B不会直接引用A。它应该是一个专注于处理其事件的唯一任务的小类,不应与A直接耦合。实际上,这就是观察者模式的重点:一种灵活的方法,可以将代码添加到A,而不会对它产生严重依赖。

如果事件处理程序必须观察作为事件源(A)的对象,那么事件回调方法应该声明一个A类型的参数,以便它可以传递给处理程序无状态方式,仅在事件处理期间使用,之后不保留。

答案 1 :(得分:0)

因为你没有按照模式正常定义它。你可以看看这个Tutorial

将监听器定义为这样的接口,例如:

/**
 * The listener interface for receiving message events. The class that is interested in processing a
 * message event implements this interface.
 *
 * @see MessageEvent
 */
public interface MessageListener
{

  /**
   * Message received notification.
   *
   * @param data the data
   * @throws IOException Signals that an I/O exception has occurred.
   */
  public void MessageReceived( byte[] data ) throws IOException;

  /**
   * Connection closed notification.
   */
  public void closed();
}

现在您可以创建所需的实现,例如:

public class MessageListenerImpl implements MessageListener
{

  /** The driver. */
  private DeviceClassInternal driver;

  /** The log. */
  private Log log;

  /**
   * Instantiates a new TCP message listener.
   *
   * @param driver the driver
   * @param log the log
   */
  public MessageListenerImpl( DeviceClassInternal driver, Log log )
  {
    this.driver = driver;
    this.log = log;
  }

  /** {@inheritDoc} */
  @Override
  public void MessageReceived( byte[] data ) throws IOException
  {
    log.info( "data received: {1}", new String( data ) );
    driver.dataReceived( data );
  }

  @Override
  public void closed()
  {
    log.info( "{0}: Connection closed", physicalConnection.getName() );
    driver.closed();
  }

}

最后你可以创建它并将这个监听器注册到你想要的对象:

  MessageListenerImpl listener = new MessageListenerImpl( connection, log ); // create the listener and you're good to go.

  physicalConnection.registerListener( listener ); // Just some object with a register function.

答案 2 :(得分:0)

这一切都取决于具体的用例。 正如其他人所指出的那样,在许多情况下,你不需要在你的听众中持有A引用,而是依赖于在事件本身中传递它。但这并不总是足够的 - 例如,您可能希望根据其他事件取消注册,并且您需要原始引用来调用removeListener。

但这是一个更通用的问题 - 如果您仍然在其他地方引用A,那么无论您的听众设计如何,它都无法获得GC。另一方面,如果你忘记了程序中无处不在的A的引用而且也没有引用B,那么无论如何它们都将被垃圾收集 - 循环引用不会阻止java中的垃圾收集({{3 }})

我过去有类似的情况,但是我很关心B是GC而不是A.当从外部对B的所有引用都丢失时,没有人可以取消注册,但它仍然会从A获得通知事件。如果你没有完全清理自己(人们经常不这样做,因为他们认为以图形方式摆脱UI组件,那么在java中的UI框架中结束这样的聆听听众是很常见的。忘记对它的所有引用应该是足够的 - 例如,一些全局键盘处理程序的监听器或某些东西仍然保存在强可达集中的所有内容)。

对于UI框架,您没有机会更改核心代码,使用您自己的代码,您可以尝试使用WeakReference而不是强引用来创建侦听器列表,并在需要时对每个通知进行一些清理。它有一个副作用,你必须让你的听众引用其他代码的地方,但无论如何这是一个很好的做法(因为你应该在其他情况下手动取消注册) - 如果你不这样做,与弱参考你会突然停止在随机时间回来(在几个gc循环运行之后)。

在任何情况下,你首先需要了解(并告诉我们),你如何想象A和B的生命周期以及为什么B在A已经消失之后将被强烈引用。您也可以随时将B-> A参考弱,但请首先确保完全理解您对什么时候被遗忘的期望。