Observer Pattern in RUBY

Overview

Mitch
5 min readSep 24, 2014

In this quick article we’re going to go over the Observer Pattern. We’ll make this as quick and as sweet as possible. The purpose is that is you ever find yourself in the need to use the pattern in your own application you’ll have a quick ‘how-to’ guide.

When to use the Observer Pattern?

The observer pattern is used when you are building a system where the state of one object effects the state of other objects. Take the weather channel for example, it’s their job to report the forcast for the day. They report this information to you, the person observing the broadcast. If the anchor on the weather channel reports that it is going to rain maybe that would mean that you can’t cut the lawn today… it doesn’t really matter. All the anchor is responsible for is reporting the weather, it’s up to you to take that information and plan your day accordingly.

This is exactly how the observer pattern works. We have one component that’s being ‘watched’ by other components. When the component being watched changes it sends out a message to the other components that basically says “ I’m different now ”, and anyone waiting for this message can change accordingly.

One of the best thought examples I’ve found of this comes from Russ Olsen in his ‘Design Patterns In Ruby’ book. Think of a spreadsheet application, like Excel, when you change the state of one cell it will effect the numbers in other cells.

Maybe you created a expenses application and you have a cell to keep track of all the money you’ve been spending. If you were to change the amount in one of the cells it would change your total sum. So, the sum is being a observer to the other cells. When one cell changes the observing cells will need to change accordingly.

Using the Observer Pattern

For our sample application we’re going to use an example that y Professor used from my Software Development class.

Let’s say we have a professor who needs to set the date for the midterm. Who will need to know about this date? Well, the Students, they’ll need to study. The Secretary, she will need to book a room. The Dean will need to know, he needs to go over the exam and make sure it’s up to course standards.

So initially our program, in the simplest form, will now look something like what we have below… try and see if you can spot the bad code?

class Professor
attr_reader :name, :class_name, :exam_date
def initialize(name, class_name, student, secretary, dean)
@name = name
@class = class_name

@student = student
@secretary = secretary
@dean = dean
end
def set_midterm(midterm_date)
@exam_date = midterm_date
@student.update(self)
@secretary.update(self)
@dean.update(self)
end
end
class Student
def update(prof)
puts “I need to start studying for Prof. #{prof.name} exam on the #{prof.exam_date}”
end
end
class Secretary
def update(prof)
puts “I will find a room for the midterm on the #{prof.exam_date}”
end
end
class Dean
def update(prof)
puts “I will go over the midterm before #{prof.exam_date}”
end
end
student = Student.new
secretary = Secretary.new
dean = Dean.new
prof = Professor.new(“Jeff Professor”, “Software Engineering”, student, secretary, dean)
prof.set_midterm(“Oct 25th”)

*Notice that Professor knows that each class observing it has an update method. He or She wouldn’t care what those methods do, just that they’re there.

output

Student: I need to start studying for Prof. Jeff Professor exam on the Oct 25th
Secretary: I will find a room for the midterm on the Oct 25th
Dean: I will go over the midterm before Oct 25t

Did you find where the code starts to smell? It should be fairly easy to see that the Professor class is taking on way too much responsibility in this application. We are instantiating the student, secretary and dean all in the professor class. Quite frankly the Professor shouldn’t really care what’s watching it. So if we wanted to add another class that’s watching the Professor we would have to change the class again, this is gonna get bad real quick.

A better approach to this would be to have a list of all the object that are observing the the Professor class. That way whenever we want to add an observer we can just add them to the array. We can achieve this by changing around the Professor class slightly. We’ll create a new array in the initialize method of the class and then create methods to add and delete them from the array.

class Professor
attr_reader :name, :class_name, :exam_date
attr_accessor :observers

def initialize(name, class_name)
@name = name
@class = class_name
@observers = []
end
def set_midterm(midterm_date)
notify_observers
end
def add_observer(observer)
@observers << observer
end
def notify_observers
observers.each do |observer|
observer.update(self)
end
end

end

This simple change has already taken a huge load off the professors shoulders, whenever we wanted to add a new observer we would simply call the add_observer method.

prof.add_observer(student)
prof.add_observer(secretary)
prof.add_observer(dean)

This code achieves the same end goal of the earlier example, but this one is much cleaner and easier to scale when we need new observers.

Going Further

we can take the refactoring a step further by factoring out everything that's specific to the observer methods. One way to accomplish this is through inheritance. We could create a class that’s called Subject specifically for this and then have the Professor class inherit from it. This would accomplish what we want but would not be the most ideal solution because Ruby doesn’t support multiple inheritance. This would use up our single precious inherited base class.

A better Rubyist way of doing this is to create a module to extract out the behavior we want.

So here is what our module will look like…

module Subject
def initialize
@observers = []
end
def add_observer(observer)
@observers << observer
end
def delete_observer(observer)
@observer.delete(observer)
end
def notify_observers
@observers.each do |observer|
observer.update(self)
end
end
end

so now professor looks something like this…

class Professor
include Subject
attr_reader :name, :class_name, :exam_date
attr_accessor :observers

def initialize(name, class_name)
super()
@name = name
@class = class_name
end
def set_midterm(midterm_date)
notify_observers
end
end

Boy! does this class ever look clean. We now have a fully scalable program were our class being observed can communicate it’s changes with anything watching it.

Thats all there is to it

So there, we can see how our shiny new observer pattern is moving the source of the news (the midterm) to all of it’s consumers.

--

--

Mitch

Engineer and Coffee enthustiast living in Ottawa