Observer Pattern

CSC-430

Phillip Wright

Observer Pattern

defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically

AKA: Publish-Subscribe, Dependents

OO Principle: Loose Coupling

Strive for loosely coupled designs between objects that interact.

Loose Coupling

  • Allows objects to interact with minimal knowledge
  • Changes to one component won’t impact another (within reason)
  • Components can be added without requiring changes to code
  • Turns dependencies into a runtime property instead of a static property

OO Principles

We will also be utilizing the previous principles we learned:

  • Encapsulate what varies
  • Favor composition over inheritance
  • Program to interfaces

Strategy Pattern

Let’s think about how the strategy pattern works real quick, and see if we can derive the observer pattern from it.

Strategy Pattern II

Whenever some action happens (e.g., a method call), we delegate to a strategy which encapsulates the desired behavior.

Observer Pattern II

Whenever some action happens (e.g., an update), we notify an observer which encapsulates the dependent behavior.

Sounds familiar…

Observer Pattern III

As long as we only have one dependency, we can see that the observer pattern and strategy pattern are basically identical.

One-To-Many

So, how do we incorporate multiple dependencies into our strategy pattern?

We just add the ability to provide multiple strategy patterns to a subject!

One-To-Many II

public class Duck{
  private final long id;
  private final Observer observer1;
  private final Observer observer2;

  public Duck(long id,Observer o1,Observer o2,Observer o3){
    // ...
  }
  public void fly(){
    observer1.notifyOfFlight(id);
    observer2.notifyOfFlight(id);
  }
}

One-To-Many III

We will also typically provide the ability to subscribe and unsubscribe observers at run times, though this is not particularly different from the strategy pattern (though we often make strategy patterns final).

One-To-Many IV

public class Duck{
  private final long id;
  private final List<Observer> observers=new ArrayList<>();
  
  public Duck(long id){
    this.id = id;
  }

  //...
}

One-To-Many V

public class Duck{
  //...
  public void fly(){
    for(final Observer o : observers){
      o.notifyOfFlight(id);
    }
  }
}

So just use strategies?

No! While the essence of these two patterns are the same, the use cases for the observer pattern often have common requirements (like (un)subscribing) and specific semantics related to when notifications should occur, etc.

Strategy vs. Observer

Observers…

  • Almost always have a greater number of dependents
  • Have a looser coupling, because the intent of the dependent is unknown
  • Focus more on the dependency and less on the computation

I.e., a subject does not care what the dependency is doing!

When To Use

You should consider using the observer pattern when:

  • An abstraction has two aspects where one is dependent on the other.
  • When a change in one components requires changes in others, but which/how many is not known.
  • When objects should be able to send/receive data without assumptions.

Problems

One issue with the observer pattern is that we really have no idea what dependents are doing. If an observer performs a large amount of computation, or cascades updates to its own observers, you can cause a huge performance hit!

Implementation Details

We also must decide whether we want to use a pull model or a push model for our updates.

Pull

  • Observers only receive an update notification and reference to the subject
  • Observers may then query the subject for just the data they need
  • Embraces the subjects ignorance of its dependents’ behaviors
  • Could be less efficient for the observer, due to the overhead of gathering data

Push

  • All of the needed data is pushed to all observers
  • Makes the coupling a bit tighter, perhaps
  • Gives the subject more control over the data