Design Patterns: The Observer Pattern

observer
Doug Yun

Engineering Manager

Doug Yun

Note: We won't be going over the Ruby module Observable. Instead, we'll building out the pattern ourselves.

Your First Day at the NSA

Welcome to the National Security Agency, Agent Smith. You have quite an impressive background, and we believe your "go-getter" attitude will instill a new kind of vigor within the organization.

Your cubicle is down to the left... here are some NDAs for you to fill out. I'll swing by your desk in the afternoon and pick them up from you later. Oh, and before I forget, here is your first assignment.

Go get 'em, tiger!

The First Assignment

Agent Smith
Spook First Class
[REDACTED]
NSA 08-20-[REDACTED]

 Operation [REDACTED] Observers

Welcome, Agent Smith:

Bluntly, we'd like to track everyone's emails.

Attached are two documents.

The first document will show you the basic structure of a typical email,
and the second document will provide you a basic profile of a suspicious
person.

If there are any questions, please reach me at [REDACTED].

Best of luck,





Agent [REDACTED]
[REDACTED]
[REDACTED]
NSA
# Document 1:
# Basic structure of an email

module Email
 extend self

 def send(subject, sender, receiver)
 puts %Q[
 Subject: #{subject}
 From: #{sender}@example.com
 To: #{receiver}@example.com
 Date: #{Time.now.asctime}
 ]
 end
end
# Document 2:
# Characteristics of a suspicious person

class Person
 include Email
 attr_reader :name

 def initialize(name)
 @name = name
 end

 def send_email(subject, receiver)
 Email.send(subject, name, receiver)
 end
end

As we look through the Email module, we see that it contains Email.send which takes three arguments: subject, sender, and receiver.

Gazing at the suspicious Person class, we see that it includes the Email module. Person#send_email takes two parameters: a subject and a receiver. Person#name will stand in as the sender of the email.

Hypothetically, let's see how a suspicious person would send an email:

bill = Person.new 'Bill'
bill.send_email 'Fishing Trip', 'Fred'
 # =>
 Subject: Fishing Trip
 From: Bill@example.com
 To: Fred@example.com
 Date: Wed Aug 16 20:35:09 2006

Hmm... as you sit in your cubicle, you ponder the numerous possible ways of tracking emails. You won't need anything too complicated, just something to kick off a notification once an email has been sent.

Volia! You realize you can use the Observer pattern!

The Subject and its Observers

First, let's start off by creating two observer classes, Alert and Agent classes.

class Alert
 def gotcha(person)
 puts "!!! ALERT: #{person.name.upcase} SENT AN EMAIL !!!"
 end
end

class Agent
 def gotcha(person)
 puts "!!! TIME TO DETAIN #{person.name.upcase} !!!"
 end
end

Next, let's create a Subject module.

module Subject
 attr_reader :observers

 def initialize
 @observers = []
 end

 def add_observer(*observers)
 observers.each { |observer| @observers << observer }
 end

 def delete_observer(*observers)
 observers.each { |observer| @observers.delete(observer) }
 end

 private

 def notify_observers
 observers.each { |observer| observer.gotcha(self) }
 end
end

Here within the Subject#initialize, we create an empty array which will contain a list of observers. Subject#add_observer simply pushes our desired observers into the array.

Finally, we can alter the suspicious Person class, which will act as the subject class. Let's include the Subject module now.

class Person
 include Email, Subject
 attr_reader :name

 def initialize(name)
 # 'super' requires a parentheses because we're calling
 # super on the superclass, 'Subject'
 super()
 @name = name
 end

 def send_email(subject, receiver)
 Email.send(subject, name, receiver)
 notify_observers
 end
end

Subject#notify_observers calls #gotcha on each observer, which informs each observer that Person#send_email has been kicked off.

Now let's give it a whirl...

alert = Alert.new
agent = Agent.new

bill = Person.new 'Bill'

bill.add_observer alert, agent # Bill now has two observers watching him

bill.send_email 'Fishing Trip', 'Fred'
 # =>
 Subject: Fishing Trip
 From: Bill@example.com
 To: Fred@example.com
 Date: Wed Aug 16 20:35:09 2006

!!! ALERT: BILL SENT AN EMAIL !!!
!!! TIME TO DETAIN BILL !!!

Perfect, it works! Now we can start protecting our freedom!

Discussion

In our example above, we have two observers, the Alert and Agent classes, and a subject, Person. By creating the Subject module, any instance of Person now informs and updates any observer through #notify_observers, ultimately removing any implicit coupling from Alert and Agent.

There are a few similarities between the Observer and Strategy patterns. Both patterns employ an object (the Observer's subject and the Strategy's context) that makes calls to another object (the Observer's observer or Strategy's strategy). The difference between the two patterns is the purpose and use case. The Strategy pattern relies on the strategy to do the work, while the Observer pattern informs the observers of what is going on with the subject.

Hope you enjoyed this short example, thanks for reading!

Newsletter

Stay in the Know

Get the latest news and insights on Elixir, Phoenix, machine learning, product strategy, and more—delivered straight to your inbox.

Narwin holding a press release sheet while opening the DockYard brand kit box