How to implement an abstract class in Ruby

Viewed 107613

I know there is no concept of an abstract class in Ruby. But if it needs to be implemented, how do I go about it? I tried something like this:

class A
  def self.new
    raise 'Doh! You are trying to write Java in Ruby!'
  end
end

class B < A
  ...
  ...
end

But, when I try to instantiate B, it is internally going to call A.new which is going to raise the exception.

Also, modules cannot be instantiated, but they cannot be inherited too. Making the new method private will also not work.

Does anyone have any pointers?

17 Answers

I don't like using abstract classes in Ruby (there's almost always a better way). If you really think it's the best technique for the situation though, you can use the following snippet to be more declarative about which methods are abstract:

module Abstract
  def abstract_methods(*args)
    args.each do |name|
      class_eval(<<-END, __FILE__, __LINE__)
        def #{name}(*args)
          raise NotImplementedError.new("You must implement #{name}.")
        end
      END
      # important that this END is capitalized, since it marks the end of <<-END
    end
  end
end

require 'rubygems'
require 'rspec'

describe "abstract methods" do
  before(:each) do
    @klass = Class.new do
      extend Abstract

      abstract_methods :foo, :bar
    end
  end

  it "raises NoMethodError" do
    proc {
      @klass.new.foo
    }.should raise_error(NoMethodError)
  end

  it "can be overridden" do
    subclass = Class.new(@klass) do
      def foo
        :overridden
      end
    end

    subclass.new.foo.should == :overridden
  end
end

Basically, you just call abstract_methods with the list of methods that are abstract, and when they get called by an instance of the abstract class, a NotImplementedError exception will be raised.

Try this:

class A
  def initialize
    raise 'Doh! You are trying to instantiate an abstract class!'
  end
end

class B < A
  def initialize
  end
end
class A
  private_class_method :new
end

class B < A
  public_class_method :new
end

In the last 6 1/2 years of programming Ruby, I haven't needed an abstract class once.

If you're thinking you need an abstract class, you're thinking too much in a language that provides/requires them, not in Ruby as such.

As others have suggested, a mixin is more appropriate for things that are supposed to be interfaces (as Java defines them), and rethinking your design is more appropriate for things that "need" abstract classes from other languages like C++.

Update as of 2022: I haven’t needed abstract classes in Ruby in 20 years of use. Everything that all of the folks commenting on my response are saying is addressed by actually learning Ruby and using the appropriate tools, like modules (which even give you common implementations). There are people on teams I have managed who have created classes that have base implementation that fail (like an abstract class), but these are mostly a waste of coding because NoMethodError would produce the exact same result as an AbstractClassError in production.

What purpose are you trying to serve with an abstract class? There is probably a better way to do it in Ruby, but you didn't give any details.

My pointer is this; use a mixin not inheritance.

Personally, I raise NotImplementedError in methods of abstract classes. But you may want to leave it out of the new method, for the reasons you mentioned.

If you want to go with an uninstantiable class, in your A.new method, check if self == A before throwing the error.

But really, a module seems more like what you want here. For example, Enumerable is the sort of thing that might be an abstract class in other languages. You technically can't subclass them, but calling include SomeModule achieves roughly the same goal. Is there some reason this won't work for you?

Another answer:

module Abstract
  def self.append_features(klass)
    # access an object's copy of its class's methods & such
    metaclass = lambda { |obj| class << obj; self ; end }

    metaclass[klass].instance_eval do
      old_new = instance_method(:new)
      undef_method :new

      define_method(:inherited) do |subklass|
        metaclass[subklass].instance_eval do
          define_method(:new, old_new)
        end
      end
    end
  end
end

This relies on the normal #method_missing to report unimplemented methods, but keeps abstract classes from being implemented (even if they have an initialize method)

class A
  include Abstract
end
class B < A
end

B.new #=> #<B:0x24ea0>
A.new # raises #<NoMethodError: undefined method `new' for A:Class>

Like the other posters have said, you should probably be using a mixin though, rather than an abstract class.

There is nothing wrong with your approach. Raising an error in the initializer seems fine, as long as all your subclasses override initialize of course. But you don't want to define self.new like that. Here's what I would do:

class A
  class AbstractClassInstiationError < RuntimeError; end
  def initialize
    raise AbstractClassInstiationError, "Cannot instantiate this class directly, etc..."
  end
end

Another approach would be put all that functionality in a module, which as you mentioned can never be instantiated. Then, include the module in your classes rather than inheriting from another class. However, this would break things like super.

It depends on how you want to structure it, although modules seem like a cleaner solution for solving the problem of "How do I write some stuff that is deigned for other classes to use"

Though this doesn't feel like Ruby, you can do this:

class A
  def initialize
    raise 'abstract class' if self.instance_of?(A)

    puts 'initialized'
  end
end

class B < A
end

The results:

>> A.new
  (rib):2:in `main'
  (rib):2:in `new'
  (rib):3:in `initialize'
RuntimeError: abstract class
>> B.new
initialized
=> #<B:0x00007f80620d8358>
>>
Related