Fork me on GitHub

Building Interfaces and Abstract Classes in Ruby

So back in the dark ages of my career, pre-2006, I spent a long time coding Java. Yeah, I know, please don’t judge. Anyway, In Java, for those of you who are unaware were two constructs that I occasionally wish I had in Ruby, those are Interfaces and Abstract Classes. The difference between these two constructs is subtle, but important.

In Java an Interface is a basically a blueprint of methods that the class who implements the Interface needs to implement. For example:

interface Bicycle {

  void changeGear(int newValue);

  void speedUp(int increment);

  void applyBrakes(int decrement);
}

public class ACMEBicycle implements Bicycle {
   
   public void changeGear(int newValue) {
     // do some work here
   }
   
   public void speedUp(int increment) {
     // do some work here
   }
   
   public void applyBrakes(int decrement) {
     // do some work here
   }

}

Here we have a Bicycle Interface that says there are three methods that need to be implemented. It is then the responsibility of the ACMEBicycle class to implement those methods. Now, an Abstract Class in Java is similar to an Interface in that it too is a blueprint of methods that the extending class may or may not need to implement. There in lies one of the differences between the two. Let’s take a look at the same example, but this time we want to implement the same behavior of all of our extending classes for the applyBrakes method:

abstract class Bicycle {
  
  abstract public void changeGear(int newValue);

  abstract public void speedUp(int increment);

  public void applyBrakes(int decrement) {
    // do some work here
  }
  
}

public class ACMEBicycle extends Bicycle {
   
   public void applyBrakes(int decrement) {
     // do some work here
   }

}

An Abstract Class is a great way to provide a mix of fully implemented methods as well as providing subclasses with a mixture of methods that need to be implemented by the extending class.

The really powerful part of all of this is two fold. First, the Java compiler will happily yell at you and fail if it finds that you haven’t implemented some of the methods that you were told you had to. Second, you can easily see the methods that you need to document right there, you can even copy/paste their definitions right into your class so you can start to fill them out.

So, how does this bring us over to Ruby? Great question. I’d like to take a few moments and explore a few ways we can get some of this power in Ruby.

Unfortunately, or fortunately depending on how you look at it (I see it as a mixed blessing), there is no compiler in Ruby, so we don’t really have a good way of having the system yell at us if we don’t implement the methods we were supposed to. But, there is still plenty we can do to help those who are implementing our classes both know what they need to implement and to find out what they haven’t implemented when their program is executing.

Here is one implementation on we can gain a bit of that functionality back in Ruby:

module AbstractInterface
  
  class InterfaceNotImplementedError < NoMethodError
  end
  
  def self.included(klass)
    klass.send(:include, AbstractInterface::Methods)
    klass.send(:extend, AbstractInterface::Methods)
  end
  
  module Methods
    
    def api_not_implemented(klass)
      caller.first.match(/in \`(.+)\'/)
      method_name = $1
      raise AbstractInterface::InterfaceNotImplementedError.new("#{klass.class.name} needs to implement '#{method_name}' for interface #{self.name}!")
    end
    
  end
  
end

class Bicycle
  include AbstractInterface
  
  # Some documentation on the change_gear method
  def change_gear(new_value)
    Bicycle.api_not_implemented(self)
  end
  
  # Some documentation on the speed_up method
  def speed_up(increment)
    Bicycle.api_not_implemented(self)
  end
  
  # Some documentation on the apply_brakes method
  def apply_brakes(decrement)
    # do some work here
  end
  
end

class AcmeBicycle < Bicycle
end

bike = AcmeBicycle.new
bike.change_gear(1) # AbstractInterface::InterfaceNotImplementedError: AcmeBicycle needs to implement 'change_gear' for interface Bicycle!
view raw gistfile1.rb This Gist brought to you by GitHub.

What we’ve done here is to inject a Module into our Bicycle class to give it a nice error it can raise and a little bit of help building a nice error message for the user. Then in our Bicycle class we define all the methods we want and in the ones we need the end user to define we can call the api_not_implemented method and it will raise the AbstractInterface::InterfaceNotImplementedError error for us.

We could simplify this a bit by having a nice little helper macro that we can use to build these methods, like this:

module AbstractInterface
  
  class InterfaceNotImplementedError < NoMethodError
  end
  
  def self.included(klass)
    klass.send(:include, AbstractInterface::Methods)
    klass.send(:extend, AbstractInterface::Methods)
    klass.send(:extend, AbstractInterface::ClassMethods)
  end
  
  module Methods
    
    def api_not_implemented(klass, method_name = nil)
      if method_name.nil?
        caller.first.match(/in \`(.+)\'/)
        method_name = $1
      end
      raise AbstractInterface::InterfaceNotImplementedError.new("#{klass.class.name} needs to implement '#{method_name}' for interface #{self.name}!")
    end
    
  end
  
  module ClassMethods
    
    def needs_implementation(name, *args)
      self.class_eval do
        define_method(name) do |*args|
          Bicycle.api_not_implemented(self, name)
        end
      end
    end
    
  end
  
end

class Bicycle
  include AbstractInterface
  
  needs_implementation :change_gear, :new_value
  needs_implementation :speed_up, :increment
  
  # Some documentation on the apply_brakes method
  def apply_brakes(decrement)
    # do some work here
  end
  
end

class AcmeBicycle < Bicycle
end

bike = AcmeBicycle.new
bike.change_gear(1) # AbstractInterface::InterfaceNotImplementedError: AcmeBicycle needs to implement 'change_gear' for interface Bicycle!
view raw gistfile1.rb This Gist brought to you by GitHub.

That approach certainly makes our code look a bit cleaner, I’m not denying that, however it has one really big flaw, at least for me anyway, it doesn’t give us a good to place to hang our documentation hat. In the previous approach we had actual methods that we could then document and that documentation would then show up in RDoc when it’s outputted. With the latter approach, however, we can document the hell out of the needs_implementation calls we have in the Bicycle class, but they won’t ever show up in the documentation. That means that users of our library have to crack open the actual code itself to see what it they are expected to implement.

Another approach we could’ve taken, which I bother to demonstrate here as I don’t think it offers a better approach is to have the needs_implementation method collect up the names of those methods and use method_missing to report that the method needs to be implemented. I mention it here only for completeness, but it definitely is not the best solution to this problem.

Finally, I would like to note that, as far as I can see, there is no way in Ruby to create a callback hook for when a class has been defined. If there was in fact such a hook we could use to it immediately notify the end user that they have forgotten to implement certain methods. Perhaps in Ruby 2.0??? That’s just pure hope on my part.

That’s it. I hope you enjoyed our brief (*cough*) look through implementing Interface and Abstract Classes in Ruby. I hope you’ve enjoyed it.

* PS, yes, I’m aware I didn’t talk about multiple vs. single inheritance in either Java or Ruby, nor did I talk about the fact that in Ruby you can’t really have Abstract Classes. I thought that was all a bit much for an already rather lengthy post as it was. Perhaps another day. :)

Tags: ,

11 Responses to “Building Interfaces and Abstract Classes in Ruby”

  1. Sean Huber Says:

    Interesting and inspiring post! Here’s my stab at it: https://github.com/shuber/interface

  2. Mark Bates Says:

    Thanks Sean! You got the spirit of the post, it’s really just some food for thought and to get people thinking.

  3. Vidar Hokstad Says:

    Why? You still need testing, or you still risk running into exceptions at runtime. And if you leave the methods undefined in the class in question, you’ll get an exception when used with test cases that triggers the use of the unimplemented methods anyway. So the only difference is that you get different exceptions.

    This seems like an attempt at writing Java in Ruby, defeating much of the purpose of using Ruby in the first place.

  4. Jorge Dias Says:

    There’s a hook called inherited, that get’s called when on a super class when it’s inherited. May that work for you?

    http://apidock.com/ruby/Class/inherited

  5. Paul Gibler Says:

    I agree with Vidar. If you want the statically typedness of Java, use Java. Test Driven Development lets you handle errors far better than trying to Javafy Ruby. Still, this is a good post – some interesting stuff for sure.

  6. Mark Bates Says:

    @Jorge, the inherited, and it’s Module cousin included, are great methods that allow you to do some fantastic meta programming tricks. However, they both suffer from the same problem, they are usually at the start of the class definition. Example: User < ActiveRecord::Base is where the inherited method would get called, so it is unaware of all the method definitions that are to follow in the class you’re defining. Does that make sense? I’m saying it be great to have a class_defined hook, that would get loaded whenever the end on a class definition gets executed.

  7. Mark Bates Says:

    @Paul and @Vidar, I agree if you want a statically typed language, than Java’s a pretty good one. With that said one of things I never liked about Java was the fact it was statically typed. I’m not talking about static typing or duck typing in this post, however. What I am talking about is giving people who are implementing some library a nice template and contract to help them develop too. Testing is, of course, one of the most important parts of the development process, just read some other articles on my blog for my feelings towards testing, but you have to know what you are supposed to be testing and what needs to be implemented.

    Too many times I’ve seen developers post something like this in their README files for a library:

    Library X is easy to extend and customize! Just implement your own version of Class Y!

    That’s great, but what am I supposed to implement for Class Y? What methods? What do they do? Where’s the documentation? What if the library is updated and new methods need to be implemented? I’m simply talking about a way to help ensure people implementing these classes know what they’re implementing and they are alerted when they’ve missed something.

    I’m advocating Javafying Ruby, far from it, but it is important to remember that other languages have good features too, and that we as a community should keep our eyes out for those good features and steal the hell out of them! :)

    Finally, and I don’t think I made this clear in the original post, this is post was more of a thought exercise. “How would we implement Interfaces and Abstract Classes in Ruby?” That sort of thing.

  8. Mark Bates Says:

    Oh, and I would love to see library developers start shipping ‘shared_examples_groups’ for their libraries. That way when you go to implement Class Y you can plug in the shared_example_group for it and know that you’re doing the right thing because of those examples.

  9. Sean Huber Says:

    @Mark, Completely agree and I can definitely see the benefit of using it to test library extensions e.g.

    class CustomModel
    implements ActiveModel

    end

    I’ve updated https://github.com/shuber/interface with interface testing assertions so you can do something like

    class CustomModelTest < Test::Unit::TestCase
    def test_should_implement_active_model
    assert_implements_interface CustomModel.new, ActiveModel
    end

    # or test all interfaces
    def test_should_implement_interfaces
    assert_implements_interfaces CustomModel.new
    end
    end

    By 'shared_examples_groups' do you mean something like ActiveModel::Lint::Tests (http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/)?

  10. Steve Schwartz Says:

    This is very interesting. I was really hoping you could do something like this in your Interface module:

    def self.included(klass)
    missing = Interface::Methods.methods – klass.methods
    raise Interface::InterfaceNotImplementedError.new(“#{klass.class.name} needs to implement ‘#{missing.first}’ for interface #{self.name}!”) unless missing.size == 0
    klass.send(:include, Interface::Methods)
    klass.send(:extend, Interface::Methods)
    end

    But unfortunately, at the time of include, our klass’s methods have not yet loaded, so it would always error out. It could possibly work if we included the Interface module at the *bottom* of our class, but I that’s really not an acceptable solution.

  11. Vesa Vänskä Says:

    You can get documentation for needs_implementation -calls by using YARD and writing a custom construct plugin for it. Check http://yardoc.org/features.html under the “Custom Constructs and Extensibility” heading.