Made Tech Blog

Design Patterns: Observer

To be meaningfully involved in the architecture of any web application your team is building, a basic grasp of what design patterns are, and knowledge of the patterns most commonly used, are good tools to have. There’s a shorthand that develops naturally when you’ve understood the concepts within that are otherwise impossible to blag.

I’m a little more right than left brained, so it’s quite easy for me to get lost when the dev-speak starts flying around the office. To that end, to help myself as much as anyone else, this is the first in a series of posts I’ll be writing that focus on the subject of design patterns.

Design patterns are solutions to software design problems that are presented in an almost conceptual way. That is to say, a given design pattern has the potential to be applied to a piece of software written in any number of languages but, at a project level, it’s up to the developer to interpret that idea and make it work for them.

The first pattern I’ll be digging into is the Observer pattern, and we at Made tend to favour working in Ruby at the moment, so the examples you’ll see reflect that.

**Problem:**

You’ve been asked to build an application where a musician can sign up and create a page for their fans and potential sponsors. One of the requirements of the project is that those fans should then be able to subscribe to the musician’s page and be automatically notified via email whenever a new tour is announced.

**Solution:**

Let’s start with three classes, Musician, Fan and Sponsor. The Fan and Sponsor classes both have an #update! method the Musician class can call, but their #update! methods do different things with the data they’re given:

class Musician
  attr_reader :name, :tel_number, :tour_date, :tour_title

  def initialize(name, tel_number, fan, sponsor)
    @name = name
    @tel_number = tel_number
    @fan = fan
    @sponsor = sponsor
  end

  def new_tour(title, date)
    @tour_date = date
    @tour_title = title
    @fan.update!(self)
    @sponsor.update!(self)
  end
end

class Fan
  attr_reader :name, :email

  def update!(musician)
    email_body = "#{musician.name} announces #{musician.tour_title} on #{musician.tour_date}!"
    ## send the email
  end
end

class Sponsor
  attr_reader :name, :email

  def update!(musician)
    email_body = "#{musician.name} will be touring on #{musician.tour_date}, call #{musician.tel_number} for enquiries."
    ## send the email
  end
end

fan = Fan.new
sponsor = Sponsor.new
musician = Musician.new('Barry Manilow', '0207 444 4444', fan, sponsor)
musician.new_tour('Lock up your daughters', '01/01/2016')

This is already pretty bad – the Musician knows explicitly about the Fan and the Sponsor, and if we wanted to notify other types of subscriber, a news outlet for example, we’d need to update the Musician class to make it aware of the new NewsOutlet class.

Ideally the Musician shouldn’t care exactly which classes are affected when the #update! method is called, just that it is passing information on to its observers and that they have an #update! method.

With that in mind, what we can do to tidy this up is add two new methods, #register_observer and #notify_observer, and update the Musician class like so:

class Musician
  attr_reader :name, :tel_number, :tour_date, :tour_title

  def initialize(name, tel_number)
    @name = name
    @tel_number = tel_number
    @observers = []
  end

  def new_tour(title, date)
    @tour_date = date
    @tour_title = title
    update_observers
  end

  def register_observer(observer)
    @observers << observer
  end

  def update_observers
    @observers.each do |observer|
      observer.update!(self)
    end
  end
end

With the #register_observer method, we now have a way for observers to make themselves known to the Musician class, and we never need to update said class if a new type of observer is introduced:

fan = Fan.new
sponsor = Sponsor.new
musician = Musician.new('Kenny Loggins', '0207 555 5555')
musician.register_observer(fan)
musician.register_observer(sponsor)

Now, when we call the #new_tour method on the Musician class, #update_observers automatically ensures both the Fan and the Sponsor are notified:

musician.new_tour('Into the Danger Zone', '27/04/2017')

The benefits of this pattern are that we’re able to add new observers without needing to change the subject, and loose coupling; the subject doesn’t know anything about the observers, it simply passes the data to them and lets them get on with whatever it is they want to do with said data.

The downside there is that an observer is potentially being given a lot of data it has no use for, in this instance the Fan class currently has no use for the telephone number, but it has access to it anyway.

About the Author

Avatar for Scott Mason

Scott Mason

Software Engineer at Made Tech