4

I was checking this example of Observer Pattern.

https://www.tutorialspoint.com/design_pattern/observer_pattern.htm

It seems that there is a circular reference when Observer Pattern is used.

import java.util.ArrayList;
import java.util.List;
public class Subject {

   private List<Observer> observers = new ArrayList<Observer>();
   private int state;

   public int getState() {
      return state;
   }

   public void setState(int state) {
      this.state = state;
      notifyAllObservers();
   }

   public void attach(Observer observer){
      observers.add(observer);      
   }

   public void notifyAllObservers(){
      for (Observer observer : observers) {
         observer.update();
      }
   }    
}


public class BinaryObserver extends Observer{

  public BinaryObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }

   @Override
   public void update() {
      System.out.println( "Binary String: " + Integer.toBinaryString( subject.getState() ) ); 
   }
}

I am not posting all the other code, but from the initial look it seems that such design will almost always have a circular reference. Am I misunderstanding anything here?

Thanks,

  • Where do you see a circular reference? – Phil N DeBlanc Dec 19 '18 at 10:41
  • Subject depends on Observer and BinaryObserver depends on Subject. However that's not circular. Subject would have to depend on BinaryObserver first. – Neil Dec 19 '18 at 12:17
3

Yes, you are right. Both objects know each other if that is what your are referring to. The important thing is what they know about each other. While the BinaryObserver knows the Subject in detail the Subject knows only Observers. To be more precise the Subject doesn't know the details about your BinaryObserver it can only send messages to it that are defined in the Observer class. In that way Subject is decoupled from the details of your BinaryObserverclass.

To sum up, while the coupling in of the BinaryObserver to the Subject is very high the coupling in the opposite direction is weak.

  • I have seen some designs e.g. GSON api where GSON building fails if there are circular references (I think the exception is StackOverflowException) - but surely, this doesn't mean the Observer Pattern is wrong and cannot use utilities such as GSON? – hagubear Dec 19 '18 at 12:02
  • 1
    @hagubear This is because you cannot serialize an instance which has a reference to itself or through some chain of dependencies, itself, otherwise a tree representing said data would never end. This is more of a technical issue related to serialization and not general best practice (though admittedly avoiding circular references is best practice anyway). – Neil Dec 19 '18 at 13:20
  • I hope I could read your answer early when I learning Observer Pattern... – Niing Apr 4 at 6:27
1

You can avoid the circular reference with a slight change to the Observer interface:

interface Observer {
  void update(Subject subject);
}

The notifyAll becomes:

public void notifyAllObservers(){
  for (Observer observer : observers) {
     observer.update(this);
  }
}

There are potentially some tradeoffs, but in the example you have shown, you would only need a single Observer object for all the Subjects in the application. If you do it as the tutorial shows, you need one for every Subject. That's a lot of extra objects hanging around just to keep track of a reference which is easily passed in.

Another issue with the Observable pattern is that you can end up with memory leaks because the observed object is holding references to the observers. Usually the pattern includes a remove method but consider using WeakReference to allow objects to automatically stop listening when they fall out of scope.

1

Update: As JimmyJames pointed out in the comments, the question is about circular references and not about circular dependencies


Original Answer, where I thought we were discussing circular dependencies:

Actually, because of polymorphism, there is no circular dependency here. Let's look closer at the dependencies to see why:

  • BinaryObserver depends on Subject and Observer
  • Subject depends on Observer
  • Observer doesn't have any dependencies.

By having the Subject depend on the Observer interface rather than any concrete implementation, the Dependency Inversion Principle is being followed. This principle states:

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
  2. Abstractions should not depend on details. Details should depend on abstractions.

So, although it may look like circular dependency at a glance, it's really just polymorphing a concrete implementation that depends on Subject (BinaryObserver) to an interface with no Subject dependency (Observer), and injecting that polymorphed object into Subject's constructor. Thus, no circular dependency.

  • The BinaryObserver instance has a reference to the Subject instance. The Subject instance has a reference to the BinaryObserver instance via a list -> circular dependency. – JimmyJames Dec 19 '18 at 19:01
  • A Circular Dependency isn't about references to instances on runtime, it's about classes (or modules) depending on each other in order to function. Subject does not require BinaryOperator to function. – Adam Bates Dec 19 '18 at 19:31
  • 1
    OK, fair point on terminology but the question is about circular references not dependencies. It doesn't appear to have been edited. – JimmyJames Dec 20 '18 at 15:21
  • Ah, I missed that. Thanks for pointing out, I'll update my answer. – Adam Bates Dec 20 '18 at 23:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.