The Observer Pattern

Steven Leiva
4 min readApr 6, 2016

--

Addy Osmani has termed the Observer Pattern one of the easiest software design patterns to get started with. I agree with him. Let’s take a look at this design pattern in more depth and see if you do too.

The Setup

Let’s say that you have just bought a sparkling new car — a Tesla Model S. It’s a fairly pricy car, so you decide to go to your Bank and get a loan. They are willing to give you the loan, but only after you agree to find an Insurance company and ensure the car. (Hey — the Bank figures that they are good at pricing credit risk, but the Insurance company is better at pricing the risk of you getting into an accident).

We are now describing a world in which two objects — the Bank and the Insurance company — are very interested in the state changes of a single object — i.e., your car. This is the type of problem that the Observer Pattern is suited for.

The Problem

How do you build a system in which a list of objects are interested in the state changes / events occurring to another object?

The Naive Solution

In order to recognize the advantages of the Observer Pattern, let’s take a look at a naive solution to the above problem:

Here, we have a Car class that we can instantiate. If we end up totaling the car, we are going to have to notify the bank and the insurance company.

First, notice that we must pass in the objects that we want to notify on initialization, and there is no way to add or remove objects once the class is instantiated. In other words, we must know who to notify before we buy our car. Furthermore, those “whose” can never stop receiving notifications from us, and we can never switch these objects for others. In other words, if we want to refinance our bank loan or move insurance companies, we are out of luck.

Secondly, while we have only one method — i.e., state change / event— in which the Bank and the Insurance company are interested in, imagine how tedious and non-DRY our code would be if we had many state changes / events for which the Bank and the Insurance company have to be notified. Did you get a picture of that yet? Because I forgot to mention that the car is owned by a rental company, so company headquarters also want to be notified about everything that the Bank and the Insurance company want to be notified about — i.e., add another observer!

Language

The Observer Pattern describes a system with:

  1. A Subject
  2. One or more Observers
  3. The ability for observers to register with the subject
  4. The ability for observer to de-register with the subject
  5. The ability of the subject to notify observers

In our setup section, we describe a system with three objects — the car, the Bank, and the Insurance company.

The car is the object in whose state the Bank and the Insurance company are interested in. In Observer Pattern terminology, the car is the subject.

The Bank and the Insurance company are interested in certain events that “happen” to the car, and are therefore the observers.

Responsibilities

The Subject in the Observer Pattern does most of the heavy lifting. It is responsible for:

  1. Maintaining a List of Observers
  2. Notifying observers of events
  3. Providing an interface so that observers can register with the subject
  4. Providing an interface so that observers can de-register

The Observers, on the other hand, have only one responsibility (besides registering and de-registering):

  1. Provide an interface by which they can be notified by the subject.

It is important to note that, because we are calling each observer programmatically, they must all adhere to the same contract. This is a fancy way of saying that each observer has to implement the same method with the same parameters. Traditionally, the method called on the observers has been update.

The Observer Pattern Solution

The solution above is an implementation of the Observer Pattern for our particular problem.

Notice that, after our car’s totaled method is called, we make a call to notify_observers, which is implemented as an instance method of a car instance.

The advantages of this solution is that we do not have to know who the observers to our subject are going to be at initiation time. Each instance of car manages an array of observers. We have implemented register and deregister in order to add or delete from that array. As long as each observer responds to the update method, we can leave it up to them to decide what to do with the information.

Variations on the Observer Pattern

The push variation of the Observer Pattern is the one that we’ve implemented in the solution above. Push refers to the fact that we have pushed the entire subject to each observer, and it is their responsibility to figure out what changed and what to do about it.

Another variation is the pull method, in which we call update on each observer, but we send along more information, such as what changed, what the old information was, and what the new information is. For example

# observer.update signature is
# def update(subject, property, previous_value, new_value)
def notify_observers
observers.each do |observer|
observer.update(self, :functional, true, false)
end
end

Relatives: The Template Method, The Strategy Method

If the Observer Pattern is close to the solution you are looking for, but doesn’t feel quite right, other design patterns that might provide a more fitting solution are:

  1. The Template Method Pattern
  2. The Strategy Method Pattern
  3. The Pub / Sub Pattern

I will be going over some of these software design patterns in future posts.

That’s all for now. Let me know what you think in the comments!

--

--