Design Patterns in Ruby: Observer, Singleton

Tweet

obsingI am going to be posting a few articles related to Software Design Patterns and how they are applicable to Ruby. The first two patterns that will be covered are the Observer Pattern and the Singleton Pattern.

Observer Pattern

If you are not familiar with this pattern, no worries, it is basically a mechanism for one object to inform other ‘interested’ objects when its state changes. To be a little more descriptive, here is a direct quote from Wikipedia:

The observer pattern (aka. Dependents, publish/subscribe) is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. It is mainly used to implement distributed event handling systems.

The Observable module in the Ruby standard library provides the mechanism necessary for us to implement this pattern, and it is fairly easy to use.

The Planning

In order to use this pattern, we first need to come up with a scenario. An example would be an application that keeps track of the car mileage and reminds us of when we need to take the vehicle in for a service. It is a very simple example, but it will allow us to explore this pattern.

The Basic Structure

The first thing that we are going to do is create a basic structure for our Notifier class that will act as an observer. One item that you really need to pay attention to here is the update() method . This is the callback that the Observable module will use when notifying changes to the observer, and the method name needs to be update().

Let us start by putting together a simple Notifier class:

class Notifier
  def update()
  end
end

That is as simple as it gets. Next, let us create the structure for the subject, our Car class:

class Car
  attr_reader :mileage, :service

  def initialize(mileage = 0, service = 3000)
    @mileage, @service = mileage, service
  end

  def log(miles)
    @mileage += miles
  end
end

It contains the attributes mileage and service, and methods initialize() and log(). The initialize() method will set the initial values for the car’s current mileage and the mileage that it needs to be taken to service. The log() method will log how many miles it has been driven recently and add it to the total vehicle mileage.

Fixing the Notifier Class

Now that we have an understanding of what our Car class does, we can go ahead and fill in the logic of what we want the notifier to do:

class Notifier
  def update(car, miles)
    puts "The car has logged #{miles} miles, totaling #{car.mileage} miles traveled."
    puts "The car needs to be taken in for a service!" if car.service <= car.mileage
  end
end

This means that every time the observer gets notified, it will print to the screen a message about the mileage usage, along with an additional message about the service status if the total mileage exceeds the service mileage.

Putting It All Together

With the basic structure for the Car class completed and the Notifier class in place, the only thing left for us to do is to put the Observable module in place.

First, we need to include it in our Car class:

require 'observer'

class Car
  include Observable
  ...
end

Next, we will add an observer each time a new instance of the Car class is created. Let us modify the initialize method:

def initialize(mileage = 0, service = 3000)
  @mileage, @service = mileage, service
  add_observer(Notifier.new)
end

The last change we need to do in this class is in our log method. We need to tell the observer that our object has changed every time we log new miles:

def log(miles)
  @mileage += miles
  changed
  notify_observers(self, miles)
end

Here we are calling changed, which sets the changed state of the object (true by default). And notify_observers(self, miles), which notifies the observer of a change.

The complete Car class looks like this:

require 'observer'

class Car
  include Observable

  attr_reader :mileage, :service

  def initialize(mileage = 0, service = 3000)
    @mileage, @service = mileage, service
    add_observer(Notifier.new)
  end

  def log(miles)
    @mileage += miles
    changed
    notify_observers(self, miles)
  end
end

To summarize, here are the changes that we made to our Car class:

  • In order to use the Observable module, we first need to require it;
  • Next, we include it with include Observable;
  • When an instance of our Car class is created, an observer is added;
  • When the log() method is called, it notifies the observers by asserting that the object has changed

Running the code

Now is the fun part, let us see what happens when we create a new instance of the Car class and call log:

car = Car.new(2300, 3000)
car.log(100)
=> "The car has logged 100 miles, totaling 2400 miles traveled."
car.log(354)
=> "The car has logged 354 miles, totaling 2754 miles traveled."
car.log(300)
=> "The car has logged 300 miles, totaling 3054 miles traveled."
=> "The car needs to be taken in for service!"

First, we create an instance of the Car class with 2300 miles, and we set that it needs to be taken for service when it reaches 3000 miles.

Every time a new mileage is logged, the Notifier class outputs the mileage added as well as the tally for the miles traveled. When the total miles for the vehicle is greater than the mileage set to be taken in for service, another output is generated. Pretty cool, huh?

Singleton Pattern

The concept of the Singleton pattern is fairly straightforward: only a single instance of a class can exist. This can be useful when an application allows only one object to be instantiated for a given class.

Even though there are mixed feelings amongst developers about this pattern, it is often used in other languages, such as Java and C-based languages. In Ruby, the Singleton module in the standard library can be used to implement this pattern.

Planning

Let’s say that we need to design a class to hold configuration data for our application, and there can only ever exist one instance of this configuration. Sure, we could simulate a Singleton by creating a module, but we would have to make sure that it could not be duplicated or cloned, otherwise it would lose its purpose.

Integration

The first step in creating a Singleton class is to require and include the Singleton module in a class:

require 'singleton'

class AppConfig
  include Singleton
end

If you try to instantiate this class as you normally would a regular class, a NoMethodError exception is raised. The constructor is made private to prevent other instances from being accidentally created:

AppConfig.new

#=> NoMethodError: private method `new' called for AppConfig:Class

To access the instance of this class, we need to use the instance() method provided by the Singleton module. When this method is first called, an instance of the class is created, and all subsequent calls return the created instance. Curious to see if this is actually true?

first, second = AppConfig.instance, AppConfig.instance
first == second

#=> true

True indeed! Now that we now how it works, let’s modify the AppConfig class and add a few things.


#...

  attr_accessor :data

  def version
    '1.0.0'
  end

Here we added a data attribute that will hold the data about the configuration, and a version method that returns the current version. Putting it all together, here is the full class:

require 'singleton'

class AppConfig
  include Singleton
  attr_accessor :data

  def version
    '1.0.0'
  end
end

Congratulation, you have just implemented a Singleton pattern in Ruby! Now, let’s play with it:

AppConfig.instance.data = {enabled: true}
=> {:enabled=>true}
AppConfig.instance.version
=> "1.0.0"

second = AppConfig.instance
second.data = {enabled: false}
=> {:enabled=>false}
AppConfig.instance.data
=> {:enabled=>false}

We first set the data attribute with arbitrary values and check its version.Next, we duplicate the singleton instance, change its data value, and confirm that the value changed in the single instance.

Conclusion

This article demonstrated how the Observer Pattern and the Singleton Pattern can be used with Ruby, and I hope the examples presented here can give you the basis for implementing them on your own applications.

(Note: The diagrams in the image for this article were made with http://yuml.me)

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • https://github.com/DouglasAllen Douglas

    You have a typo in the Notifier class. <= ?

  • https://github.com/DouglasAllen Douglas

    Try require ‘cgi’ he he

  • Manuel

    Thanks, very comprehensive explanation of the Observer pattern.
    However, I dont seee why I should use the Observer module when I can implement the same functionality very easy on my own: https://gist.github.com/mhutter/5047190/revisions
    (at least in this example, as far as I’ve seen the Observer module has some more functionality)