1

I've got a situation like this:

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);
    }
}

I believe that B can never be destroyed because A is keeping a reference to it, and A can never be destroyed because B is keeping a reference to it.

This seems like a common design pattern, but nobody explains how to avoid this circular reference?

micheal65536
  • 558
  • 3
  • 6
  • 26
  • If you want to prevent circular references, just make relations between classes (here `A` and `B`) unidirectional and not bi-directional. Or add a mediator (but can be ugly at the end) ... EDIT: this questions belongs to [Software Engineering SE](http://softwareengineering.stackexchange.com/) – KarelG Apr 04 '17 at 07:08
  • @KarelG I can't make it unidirectional because B needs to be able to call methods on A (and besides, if B didn't keep a reference to A then A would be destroyed). And I can't remove the communication from A to B because B needs to know when something happens in A. – micheal65536 Apr 04 '17 at 07:14

3 Answers3

2

In practice this is solved so that the observer B does not refer to A directly. It should be a small class focused on the sole task of handling its events and shouldn't have any direct coupling to A. In fact, that is the very point of the observer pattern: a flexible way to add code to A without a hard dependency on it.

If the event handler must observe the object which is the event source (A), then the event callback method should declare an A-typed argument so it can be passed to the handler in a stateless manner, to be used only during event handling and not retained afterwards.

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
  • A is created by B to handle network stuff. The two classes are very closely linked. For example, during initialisation B calls `instanceOfA.connect()`, and later when the user wants to disconnect B calls `instanceOfA.disconnect()` before closing the UI, or if the user does something that results in sending network activity B calls `instanceOfA.sendSomething()`, and so on. So B has to have a reference to A. A must also have a reference to B so that A can tell B when, for example, a response is received. I can't see any way to separate the rest of B from the part of B that handles events from A. – micheal65536 Apr 04 '17 at 07:24
  • In that case the general support for observers is pointless. You just have two closely coupled classes. If B can even control A's connection status, that sounds like a 1-to-1 relationship between them. – Marko Topolnik Apr 05 '17 at 19:34
  • Except as I said, B is the UI class and A is the core class. How B displays stuff is none of A's business, and B could easily be replaced by another UI class, or by a class that doesn't display a UI but performs network operations automatically. Think of an instance of A as a specific type of connection to a specific type of server, and an instance of B (or anything that implements A.Listener) as something that uses that connection. – micheal65536 Apr 05 '17 at 19:37
  • So when you dispose B, obviously you need manual code that deregisters it from A, otherwise the strong reference from A to B can't disappear. Furthermore, since A acquires external resources, its disposal shouldn't be left up to GC, either. It should explicitly react to e.g. the last listener being removed by releasing the resources. Then it can be left alone to be GC'd eventually. – Marko Topolnik Apr 05 '17 at 19:47
0

Because you are not doing it as the pattern defines it normally. You can take a look at this Tutorial.

Define a Listener as an Interface like this for example:

/**
 * 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();
}

Now you can create as much implementations as you need for example like this:

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();
  }

}

and finally you can create it and register this listener to the object you want:

  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.
Nico
  • 1,727
  • 1
  • 24
  • 42
  • In what way is this different from what I'm doing? I am using an interface. You still end up with the subject keeping a reference to the observer (through the interface), and the observer keeping a reference to the subject. – micheal65536 Apr 04 '17 at 07:17
  • No you should not keep any reference. You need a undependent interface which has an Implementation that will take care of the handling of what you throw at it. Look at my last code example. I am creating a new listener object and pass this too my Object A. My listener doesn't implement A, so it has no reference. – Nico Apr 04 '17 at 07:19
0

It all depends on specific use cases. As other people pointed, in many cases you don't need to hold A reference in your listener and instead rely on having it being passed in the event itself. But this is not always enough - for example, you might want to deregister yourself depending on some other event and you need original reference to call removeListener.

But this is more generic question - if you still hold reference to A somewhere else, it won't get GCed regardless of your listener design. On the other hand, if you have forgotten references to A everywhere in your program and don't hold reference to B as well, they will be both garbage collected anyway - circular references are not preventing garbage collection in java (How does Java Garbage Collection work with Circular References?)

I had bit similar situation in the past, but I cared about B being GCed rather than A. When all references to B from outside are lost, there is nobody to unregister it, but it is still getting notified from A on every event. It is quite common to end with such danging listeners in UI frameworks in java if you don't clean up after yourself perfectly (which people often don't do, as they assume that getting rid of the UI component graphically and forgetting all references to it should be enough - while for example, some listener to global keyboard handler or something still holds everything in strongly reachable set).

While for UI frameworks you don't get a chance to change core code, with your own code you can try to make listener lists having WeakReference instead of strong references and do some cleanup on every notification if needed. It has a side effect of you having to keep your listeners references from other places of code, but it is a good practice anyway (as you should be deregistering them manually in other case) - if you don't do that, with weak reference you would suddenly stop getting called back at random times (after few gc cycles have run).

In any case, you first need to understand (and tell us), how do you imagine lifecycle of A and B and why B will be strongly referenced after A could already disappear. You can always make B->A reference weak as well, but please first be sure to fully understand your expectations about what gets forgotten at what point.

Community
  • 1
  • 1
Artur Biesiadowski
  • 3,595
  • 2
  • 13
  • 18
  • This is a GUI application, yes. B is a UI class which will be instantiated by the UI framework. B creates A and registers itself as a listener. A should exist for as long as B exists, but is not needed (yet) once B is no longer needed. But because B holds a reference to A, and A holds a reference to B (as a listener), neither will be garbage collected even after the UI framework has removed all its references to B. I'm also running into more problems now, because B is creating more objects and passing them to the UI framework, and those objects also register themselves as listeners to A. – micheal65536 Apr 04 '17 at 07:39
  • So there my situation would be perfect as the listener implementation would be between A and B like this B <- Listener <- A. When starting A you create the Listener that holds a reference to B and give it to A. When A informs the listener it can communicate with B. B started A so it should always communicate with it while A communicates over the listener as delegate. – Nico Apr 04 '17 at 08:12
  • 1
    That should not be a case, unless you have strong references to A from somewhere else. If you get rid of B completely and A is referenced only from there, they will get GCed. There is a good chance that other objects you have mentioned are staying strongly referenced, holding reference to A which in turns hold reference to B - but it means that B->A reference is not a culprit here (situation would be the same, regardless if it does exist or not). If it this the case, I'm afraid that only solution will be to do a proper, manual cleanup on B getting disposed, deregistering other references to A. – Artur Biesiadowski Apr 04 '17 at 08:13