Log in
26
January

How to Enforce Interfaces in Ruby

Written by admin. No comments Posted in: Ruby

First, create the interface as a “mixin” (a Ruby module that will be included into another).

module Sociable
  @@must_have = [:smile, :feign_interest, :paraphrase, :laugh_at_bad_jokes]

  class << self
    def included (b)
      @@must_have.each do |m|
        raise NotImplementedError,
          "'#{b}' must implement the '#{m}' method" unless
            b.method_defined? m
      end
    end
  end
end

Here you can see we have a list of methods that must be implemented by the including class in @@must_have. Sociable.include is called automatically whenever another class or module includes Sociable.

Below is a class that is to implement Sociable:

require 'sociable'

class Salesman
  include Sociable
end

Now the class Salesman won’t load until it has implemented all of the methods in @@must_have. However there is one gotcha! Let’s say we do implement the methods as such:

require 'sociable'

class Salesman
  include Sociable

  @@paraphrases = ['Totally','I hear that','Absolutely','Uh-hu']

  def smile
    puts ":-)"
  end

  def feign_interest
    puts ":-o"
  end

  def paraphrase
    puts @@paraphrases[rand @@paraphrases.length]
  end

  def laugh_at_bad_jokes
    "o(∩_∩)o...哈哈" # Chinese for "haha"
  end
end

But oops! When we require ‘salesman’ we get a huge problem:

NotImplementedError: 'Salesperson' must implement the 'smile' method

What happened? As it turns Ruby does not look ahead to see which methods are defined within the module before it makes the actual call to Sociable.include. This requires us to move that include to the bottom of our class… which is kind of ugly. If anyone knows how to avoid this please leave a comment! In any case here is what the Salesman class should look like:

require 'sociable'

class Salesman
  @@paraphrases = ['Totally','I hear that','Absolutely','Uh-hu']

  def smile
    puts ":-)"
  end

  def feign_interest
    puts ":-o"
  end

  def paraphrase
    puts @@paraphrases[rand @@paraphrases.length]
  end

  def laugh_at_bad_jokes
    "o(∩_∩)o...哈哈" # Chinese for "haha"
  end

  include Sociable
end

So, how do we ensure that a class does indeed conform to an interface? After all, the class may very well not include Sociable at all. Well, we do have a way to check up on this.

  Salesman.included_modules.include? Sociable

This line will return true or false and will allow us to exit gracefully if the module we are dealing with doesn’t implement an interface that we expect it to.

Please leave your comments or suggestions for improvements.

0 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

Some HTML is OK

or, reply to this post via trackback.