Skip to content

Observer

The observer is one of the most important and frequently used patterns in practice. It describes how to create a one-to-many dependency in such a way that others are informed about the update of the state of one of the objects.

An example of such a pattern could be a single channel on a communicator. There can be many users on a channel, and the status update by one of them (i.e. sending messages to the channel) causes other users to get information about this fact (i.e. the sent message appears in the message window).

observer

Construction

In order to build a one-to-many relationship, we need:

  • interface or abstract class common to all observers
  • an object that you can observe (the channel on the communicator in the example above), often called observable or subject

In addition, there must be a way to:

  • connect the observer with the observed object
  • the observer could react to the change of state.

Example

The example is also based on posting a message to a channel. It consists of the following classes:

  • BaseObserver - an abstract class representing the observer (the part of the relationship that subscribes to the observable). This class takes in the constructor a reference to the ChatChannel class and subscribes to it immediately (i.e. joins)
  • UserObserver - extends the BaseObserver class. It reacts to sent messages from other users, but not of the ADMIN user type.
  • AdminObserver - it also extends the BaseObserver class and always reacts to sent messages, regardless of user type.
  • ChatChannel - class representing the messenger channel. The channel uses subscriptions. It has the ability to inform all followers about the change in status.
public abstract class BaseObserver {

  protected final ChatChannel chatChannel;

  public BaseObserver(final ChatChannel chatChannel) {
    this.chatChannel = chatChannel;
    chatChannel.subscribe(this);
  }

  public abstract void handleMessage(String message, String userType);

  public abstract String getObserverType();

  public void sendMessage(final String message) {
    chatChannel.sendMessage(message, getObserverType());
  }
}
public class UserObserver extends BaseObserver {

  private final String userName;

  public UserObserver(final ChatChannel chatChannel, final String userName) {
    super(chatChannel);
    this.userName = userName;
    System.out.println(userName + " is joining the " + chatChannel.getName());
  }

  @Override
  public void handleMessage(final String message, final String userType) {
    if (!userType.equals("ADMIN")) {
      System.out.println(userName + " sees message " + message);
    }
  }

  @Override
  public String getObserverType() {
    return "USER";
  }
}
public class AdminObserver extends BaseObserver {

  private final String adminName;

  public AdminObserver(final ChatChannel chatChannel, final String adminName) {
    super(chatChannel);
    this.adminName = adminName;
    System.out.println(adminName + " joined " + chatChannel.getName() + " as admin.");
  }

  @Override
  public void handleMessage(final String message, final String userType) {
    System.out.println(adminName + " sees " + message + " from user whose type is " + userType);
  }

  @Override
  public String getObserverType() {
    return "ADMIN";
  }
}
import java.util.ArrayList;
import java.util.List;

public class ChatChannel {

  private final String name;

  private final List<BaseObserver> observers = new ArrayList<>();
  private final List<String> messages = new ArrayList<>();

  public ChatChannel(final String name) {
    this.name = name;
  }

  public void subscribe(final BaseObserver observer) {
    if (!observers.contains(observer)) {
      observers.add(observer);
    }
  }

  public void sendMessage(final String message, final String observerType) {
    messages.add(message);
    notifyAboutChange(message, observerType);
  }

  private void notifyAboutChange(final String message, final String observerType) {
    for (final var observer : observers) {
      observer.handleMessage(message, observerType);
    }
  }

  public String getName() {
    return name;
  }
}
public class ObserverUsage {

  public static void main(String[] args) {
    final ChatChannel chatChannel = new ChatChannel("design-patterns");

    final BaseObserver userA = new UserObserver(chatChannel, "andrew");
    final BaseObserver userB = new UserObserver(chatChannel, "ala");
    final BaseObserver adminA = new AdminObserver(chatChannel, "john");
    final BaseObserver adminB = new AdminObserver(chatChannel, "ann");

    userA.sendMessage("Hello all");
    userB.sendMessage("Hi Andrew");
    adminA.sendMessage("ann they can't see what we write");
    adminB.sendMessage("Yes I know");

    /* output of the program:
      andrew is joining the design-patterns
      ala is joining the design-patterns
      john joined design-patterns as admin.
      ann joined design-patterns as admin.
      andrew sees message Hello all
      ala sees message Hello all
      john sees Hello all from user whose type is USER
      ann sees Hello all from user whose type is USER
      andrew sees message Hi Andrew
      ala sees message Hi Andrew
      john sees Hi Andrew from user whose type is USER
      ann sees Hi Andrew from user whose type is USER
      john sees ann they can't see what we write from user whose type is ADMIN
      ann sees ann they can't see what we write from user whose type is ADMIN
      john sees Yes I know from user whose type is ADMIN
      ann sees Yes I know from user whose type is ADMIN
     */
  }
}