Yianna Kokalas

Design Patterns: Observer

28 Aug 2017

What is an Observer Pattern?

Design patterns are abstract concepts to guide your development not force you into specific implementation. Modern day programs that employ some sort of Observer pattern are more of a nod to the pattern rather than a strict implementation.

The observer pattern is a useful pattern for subscribing to a state in order to be alert of state changes. This is an incredibly common design pattern that you probably use everyday at a high level.

In Javascript the Observer Pattern is used to subscribe event listeners. Java also uses this pattern for listening for clicks in their gui framework.

How does it work?

The Observer pattern will have a state that is being watched for changes by it’s subscribed objects. When the changes occur the subscribed objects will receive an update of the state change and handle it accordingly. In order to drive home this idea we will use the concept of a Bank Account and will be written in Ruby.

Interfaces

An interface in Java is a way of sharing behavior to class objects. When defining these methods they do not return any value and have no method bodies. In fact, when you try to compile your program it will result in an error unless you have defined the method yourself in the class that implements the interface.

public interface Observer {
  void update(String balance, String minimum_balance);
}

public interface Notifier {
  void send_notification(String balance, String minimum_balance);
}

class SMSNotifier implements Observer, Notifier {
  public void update(String balance, String minimum_balance) {
    /* Add method body */
  }

  public void send_notification(String balance, String minimum_balance) {
    /* Add method body */
  }
}

In Ruby we can do something similar by using modules as mixins and include a call to raise inside of the defined methods.

module ContractualBehavior
  def promise_youll_implement_me
    raise NotImplementedError, "But you promised!"
  end
end

class Contractee
  include ContractualBehavior
end

[4] pry(main)> Contractee.new.promise_youll_implement_me
NotImplementedError: But you promised!
from (pry):3:in `promise_youll_implement_me'
[5] pry(main)>

You should expect to see an error NotImplementedError: But you promised!. We use the concept of an interface in Ruby by defining modules that will raise an error whenever the mixed in behavior gets invoked. You’ll notice the huge difference between an interface in java and a module in Ruby is that in Ruby it’s not required to define the methods without a body.

The value in using an interface pattern with Ruby is the flexibility of behavior. It doesn’t care what you put in the message body. Only that you have defined the mixed in method.

Example in Ruby

Great, now we can start coding, let’s start with creating our program environment. We need to create a project folder called bank_account and inside of that create bin, lib and config folders. Finish off with a bundle init and edit the Gemfile for an important gem called require_all.

mkdir bank_account && cd bank_account && \
> mkdir bin lib config && \
> bundle init && \
> vim Gemfile

We use the require_all gem to auto require all files inside of lib

# Gemfile
source "https://rubygems.org"

git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }

gem "require_all"

Let’s create some files, one will be config/environment.rb which is used to require all the files from lib in one file. Then we need a namespace for our code which is named after the project, lib/bank_account.rb

♥ bundle installtouch config/environment.rb lib/bank_account.rb && \
> vim config/environment.rb

Inside of our environment file we will require bundler and use it to require all default gems. That’s any gem not explicitly inside of a group. Our gemfile has only one Gem at the moment which is getting required and that is require_all. We then use require_rel which is given to us from that gem to require all files in the lib folder.

# config/environment.rb

require "bundler"
Bundler.require(:default)
require_rel "../lib"

Let’s now add some code to our bank_account file. We are going to define a class method of run in order to run our sample code. In order to ensure we set things up correctly lets output something to system out with puts.

# lib/bank_account.rb

class BankAccount
  def self.run
    puts "Hey there"
  end
end

It’s a good idea to keep to standard programming conventions so we will be using a bin folder with a file named after our project.

touch bin/bank_account.rb

Inside this file we just want to require our environment file and run our BankAccount class.

# bin/bank_account.rb
require_relative "../config/environment.rb"

BankAccount.run

Let’s make sure everything works ok.

♥ ruby bin/bank_account.rb
Hey there

Woot! It works!

Observable State

We need a state that is going to be observed. For our project that is the bank account. We want to observe the account for any kind of changes and if it hits a minimal amount that should be there the user will be alerted by SMS and Email.

We already have a BankAccount class but it needs some behavior that will make it Observable. Let’s create a module that will encapsulate methods for us to ensure our BankAccount class is observable. We can call this the interface pattern.

touch lib/observable.rb && vim lib/observable.rb
# lib/observable.rb

module Observable
  def add_observer
    raise NotImplementedError, "You must implement the instance method add_observer"
  end

  def remove_observer
    raise NotImplementedError, "You must implement the instance method remove_observer"
  end

  def notify_observer
    raise NotImplementedError, "You must implement the instance method notify_observer"
  end
end

Now lets include this behavior in our BankAccount class. What’s happening here is we are not giving it specific implementation but enforcing a method to be implemented which in turn will ensure that BankAccount is observable. We do this by raising a NotImplementedError.

Lets include our Observable interface to the BankAccount class.

class BankAccount
  include Observable

  def self.run
    puts "Hey there"
  end
end

Now let us play with this code a bit. This will require a gem called pry which you can install with gem install pry. This is a debugging tool for ruby that has some useful helpers to aid in your development.

♥ pry
[1] pry(main)> require_relative './config/environment.rb'
=> true
[2] pry(main)> ls BankAccount
Observable#methods: add_observer  notify_observer  remove_observer
BankAccount.methods: run
[3] pry(main)>

Awesome, you can see that we now have an interface to make it Observable. When you use ls BankAccount we are listing all the instance variables, instance methods and class methods that are encapsulated by BankAccount. Now what happens when we try to call one of these methods?

[2] pry(main)> require_relative './config/environment.rb'
=> true
[3] pry(main)> BankAccount.new.add_observer
NotImplementedError: You must implement the instance method add_observer
from /Users/yianna/Development/code/design_patterns/bank_account/lib/observable.rb:3:in `add_observer'
[4] pry(main)> exit

We now get an error that this behavior needs to be implemented. Another way to enforce behavior is by writing tests which we will go over at the end of the post.

Defining behavior from the flowchart into our BankAccount class.

bank account observer pattern

In order to get rid of these errors we will define the three core methods that are necessary for this pattern to work.

class BankAccount
  attr_accessor :balance
  attr_writer :changed
  attr_reader :minimum_balance

  include Observable

  def initialize(balance)
    @balance = balance
    @observers = []
    @minimum_balance = 1000
    @changed = false
  end

  def add_observer(observer)
    @observers.push(observer)
  end

  def remove_observer
    @observers.delete(observer)
  end

  def notify_observers
    @observers.each { |o| o.update(balance, minimum_balance) }
    self.changed = false
  end

  ...

end

Great, now that we have our essential methods in place we can implement the rest of our BankAccount class. Lets define minimum_balance? and inside of the withdraw method lets check if we have enough money in the account to do the withdrawl. If we do have enough then we check for the minimum balance and that the account indeed did change. If we hit the minimum balance and it has changed then we will notify the observers.

class BankAccount
  
  ...

  def minimum_balance?
    balance < minimum_balance
  end

  def withdraw(amount)
    unless amount > balance
      self.balance -= amount
      self.changed = true
    end

    if minimum_balance? && changed?
      notify_observers
    end
  end

  def changed?
    @changed
  end

  def self.run
    bank_account = BankAccount.new(1200)

    SMSNotifier.new(bank_account)
    EmailNotifier.new(bank_account)

    bank_account.withdraw(300)
    bank_account.withdraw(300)
  end
end

Now if we run our code we should get an unitialized constant error. Let’s use this error to guide the next step of our development and define our observers.

♥ ruby bin/bank_account.rb
uninitialized constant BankAccount::SMSNotifier (NameError)

Defining interfaces for our observers

Let’s define two interfaces, one is the observer interface and the other is the notifier interface. Both are enforcing specific behavior that will help us update the observer and send notifications.

♥ touch lib/observer.rb lib/notifier.rb
# lib/observer.rb

module Observer
  def update
    raise NotImplementedError, "You must implement the instance method update"
  end
end
# lib/notifier.rb

module Notifier
  def send_notification(balance)
    raise NotImplementedError, "Hey define a send_notification instance method."
  end
end

Adding our observers to the BankAccount class

In order to subscribe the osbervers to our observable class of BankAccount we can use an instsantiated bank account as an argument for SMSNotifier and EmailNotifier. When initializing with a bank account, we will call the add_observer method onto the back account while passing self as an argument; self is referring to the currently instantiated class, in our case it will either be SMSNotifier or EmailNotifier.

class SMSNotifier
  def initialize(bank_account)
    bank_account.add_observer(self)
  end
end
class EmailNotifier
  def initialize(bank_account)
    bank_account.add_observer(self)
  end
end

Defining the behaviors for our observers

Let’s include our Observer and Notifier interfaces into the observer classes. This will require us to override the update and send_notification methods.

# lib/sms_notifier.rb

class SMSNotifier
  include Observer
  include Notifier

  def initialize(bank_account)
    bank_account.add_observer(self)
  end

  def update(balance, minimum_balance)
    send_notification(balance, minimum_balance)
  end

  def send_notification(balance, minimum_balance)
    puts %Q(
      Hey, your current balance is #{balance} which is below your
      minimum_balance of #{minimum_balance}.
    )
  end
end

Note: %Q preserves whitespace and allows string interpolation.

# lib/email_notifier.rb

class EmailNotifier
  include Observer
  include Notifier

  def initialize(bank_account)
    bank_account.add_observer(self)
  end

  def update(balance, minimum_balance)
    send_notification(balance, minimum_balance)
  end

  def send_notification(balance, minimum_balance)
    puts %Q(
      Hey, your current balance is #{balance} which is below your
      minimum_balance of #{minimum_balance}.
    )
  end
end

Running the code

All that’s left is to run our code. You should get the same output as below.

♥ ruby bin/bank_account.rb 

      Hey, your current balance is 900 which is below your
      minimum_balance of 1000
    

      Hey, your current balance is 900 which is below your
      minimum_balance of 1000
    

      Hey, your current balance is 600 which is below your
      minimum_balance of 1000
    

      Hey, your current balance is 600 which is below your
      minimum_balance of 1000

Setting up RSpec

Let’s set up some tests

# Gemfile

gem "rspec"
♥ bundle install
♥ rspec --init
# spec/spec_helper.rb

require_relative '../config/environment.rb'

RSpec.configure do |config|
  config.expect_with :rspec do |expectations|
    expectations.include_chain_clauses_in_custom_matcher_descriptions = true
  end

  config.mock_with :rspec do |mocks|
    mocks.verify_partial_doubles = true
  end

  config.shared_context_metadata_behavior = :apply_to_host_groups

end

Adding tests with RSpec

If you would like to skip implementing interfaces you can write tests that will check to see if those methods have indeed been defined. In the following example we use RSpec’s respond_to method to test that it can respond to any method specified as a symbol.

# spec/bank_account_spec.rb

require_relative './spec_helper'

RSpec.describe BankAccount do
  let(:bank_account) { BankAccount.new(1000) }

  it "Will respond to an instance method of add_observer" do
    expect(bank_account).to respond_to(:add_observer)
  end

  it "Will respond to an instance method of remove_observer" do
    expect(bank_account).to respond_to(:remove_observer)
  end

  it "Will respond to an instance method of notify_observers" do
    expect(bank_account).to respond_to(:notify_observers)
  end
end
♥ rspec
...

Finished in 0.00749 seconds (files took 0.26276 seconds to load)
3 examples, 0 failures

Awesome, all of our tests pass. Just to recap; The observer pattern is best when you’re solving a problem that needs to happen in response to a change in state.

Thanks for reading!

Tweet me @yonk_nyc if you like this post.

Tweet