Variation of Java observer pattern or Publisher-Subscriber Pattern

Viewed 897

What's the actual observer pattern from below 2 variations.

  1. In the first one, the Observer takes the responsibility of subscription.
  2. But in the second scenario, Publisher takes the responsibility to add a particular observer to the subscriber list.

Because, there are some implementations in the web, that take these both as observer pattern in java.

    1.
    // Observer is taking the responsibility of subscribing 
    // Observer.java
    @Override
    public void subscribe(MessagePublisher publisher) {
        publisher.getObservers().add(this);
    }

2.

    // Publisher is taking the observer to subscribe
    // Publisher.java
     @Override
     public void addObserver(Observer observer) {
       observers.add(observer);
     }
4 Answers

I suspect that they both are. They both will have to implement the Observer interface.

The second example is implementing what is known as the Observer Pattern. (the Observer) want to get notified about some event that occurred in the observed object X (the Observable/event source), right? Then you would tell the Observable to notify you. Or in other words, you start to listen to the observer for a notification - that's why the observer is also called listener.
The Observable doesn't know all it's subscribers. It only knows the listeners by their e.g., IObserver interface, which defines the actual notification callback. The Observable just adds the Observers to a container or collection.

The first example doesn't make sense. The observable calls a method on the observer to make him subscribe? That design flow must be weird.

Let's say you have a FileWriter class which exposes an event FileWriter.Completed. You have another class FileHandler that manages all files. The FileHandler calls FileWriter.write(filePath, data).

After each write operation the FileHandler has to show a message to the user. So the FileWriter is used by the FileHandler, but without knowing who's calling the write method. The FileHandler, one of the many consumers of FileWriter.write(string, string), knows that FileWriter implements an observable interface called IWriteToFileCompletedEventand defines the methodFileWriter.subscribeToWriteToFileCompleted(IObserver eventListener)`.

This is how the real implementation would look like in 'real life'

// The interface for the publisher or observable of a special event
interface IWriteToFileCompletedEvent {
  void subscribeToWriteToFileCompleted(IWriteToFileCompletedListener observer);
  void unsubscribeToWriteToFileCompleted(IWriteToFileCompletedListener observer);
}

// The interface for the observer of a special event
interface IWriteToFileCompletedListener {
  void notify(string filePathOfCompletedFile);
}

// The observable that exposes a special event
class FileWriter implements IWriteToFileCompletedEvent {
  private List<IWriteToFileCompletedListener> writeToFileCompletedListeners = new ArrayList<>();

  public void subscribeToWriteToFileCompleted(IWriteToFileCompletedListener observer) {
    this.writeToFileCompletedListeners.add(observer);
  }

  public void unsubscribeToWriteToFileCompleted(IWriteToFileCompletedListener observer) {
    this.writeToFileCompletedListeners.remove(observer);
  }

  public void writeToFile(string filePath, string data) {
    // Write data to file
    // Once done notify all listeners of the write completed event
    for (IWriteToFileCompletedListener observer : this.writeToFileCompletedListeners) {
      observer.notify(filePath);
    }
  } 
}

// The observer of a special event
class ContactsHandler implements IWriteToFileCompletedListener {
  private FileWriter fileWriter = new FileWriter();

  public void saveUserContactToFile(string filePath, string userInput) {
    this.fileWriter.subscribeToWriteToFileCompleted(this);
    this.fileWriter.writeToFile(filePath, userInput);
  }

  // Implementation of interface IWriteToFileCompletedListener 
  public void notify(string filePath) {
    this.fileWriter.unsubscribeToWriteToFileCompleted(this);
    this.messageDialog.show("The new contact was successfully saved to " + filePath);
  }
}

// Another observer of a special event
class SettingsHandler implements IWriteToFileCompletedListener {
  private FileWriter fileWriter = new FileWriter();

  public void saveUserSettingsToFile(string filePath, string userSettings) {
    this.fileWriter.subscribeToWriteToFileCompleted(this);
    this.fileWriter.writeToFile(filePath, userSettings);
  }

  // Implementation of interface IWriteToFileCompletedListener 
  public void notify(string filePath) {
    this.fileWriter.unsubscribeToWriteToFileCompleted(this);
    this.messageDialog.show("The new settings were successfully saved to " + filePath);
  }
}

The classes would interact like this

public void main(strng[] args {
  SettingsHandler settingsHandler = new SettingsHandler();
  ContactsHandler contactsHandler = new ContactsHandler();

  // Imaging this method receives user input fromo the UI:
  string newContact = textBox.gettext();
  this.contactsHandler.saveUserContactToFile("C:\Contacts.txt", newContact);

  // While waiting for the message to show the user adjusted some settings and clicked 'save'
  string changedSettings = getChangedSettings();
  this.settingsHandler.saveUserSettingsToFile("C:\UserSettings.txt", changedSettings);

  // After a while the user sees the messages send from the event listeners.
}

Multiple objects of totally different type (SettingsHandler and ContactsHandler) but common base/interface type have subscribed to the same event of the event source. Everything is clean. The object that uses the event source object actually subscribes/listens. The class that can be observed, the observable, just notifies all listeners. There is no other interaction.

To wrap it up: your solution 1 is not convenient at all (although you can make it work) and produces ugly code.

Solution 2 is actually follows the pattern that describes how to implement events. This implementation is proved and is well established, since it achieves the goal of introducing events and produces clean and readable code.

Whether observers should register themselves or have another entity handle this responsibility is an implementation decision when applying the Observer pattern, rather than a structural aspect of the pattern itself. For this reason, it's not surprising to encounter both variations.

In the pattern structure documented in the GoF book and elsewhere, the Subject has an Attach (and Detach) method as part of its interface. The implication is that any entity with a reference to a subject can call Attach and perform this registration.

Both are valid implementation of the Observer pattern, however unless you have an explicit need to expose the list of observers, the 2nd approach is preferable.

Related