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!