views:

4359

answers:

11

I know there is no concept of abstract class in ruby. But if at all it needs to be implemented, how to go about it? I tried something like...

class A
  def self.new
    raise 'Doh! You are trying to instantiate an abstract class!'
  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. Any pointers?

+2  A: 

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.

Zack
But then how to stop it from being instantiated?
Chirantan
Personally I'm just getting started with Ruby, but in Python, subclasses with declared __init___() methods don't automatically call their superclasses' __init__() methods. I would hope there would be a similar concept in Ruby, but like I said I'm just getting started.
Zack
In ruby parent's `initialize` methods don't get called automatically unless explicitly called with super.
Mk12
+1  A: 

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 self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    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
      end
    end
  end
end

require 'rubygems'
require 'spec'

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

      abstract_methods :foo, :bar
    end
  end

  it "raises NotImplementedError" do
    proc {
      @klass.new.foo
    }.should raise_error(NotImplementedError)
  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.

nakajima
This is more like an interface actually but I get the idea. Thanks.
Chirantan
+1  A: 

Nothing wrong with your approach. Raise an error in initialize seems fine, as long as all your subclasses override initialize of course. But you dont 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 instiated. Then include the module in your classes rather than inheriting from another class. However, this would break things like super.

So 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"

Squeegy
I wouldn't want to do that, either. The children can't call "super", then.
Austin Ziegler
+4  A: 

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
Andrew Peters
this is fine +1
Yar
If you want to be able to use super in `#initialize` of B, you can actually just raise whatever in A#initialize `if self.class == A`.
Mk12
A: 

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?

Chuck
A: 

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.

jshen
+1  A: 

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.

rampion
+3  A: 

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++.

Austin Ziegler
+1  A: 
class A
  private_class_method :new
end

class B < A
  public_class_method :new
end
bluehavana
Additionally one could use the inherited hook of the parent class to make the constructor method automatically visible in all subclasses: def A.inherited(subclass); subclass.instance_eval { public_class_method :new }; end
t6d
Very nice t6d. As a comment, just make sure its documented, as that is surprising behaviour (violates least surprise).
bluehavana
+3  A: 

Just to chime in late here, I think that there's no reason to stop somebody from instantiating the abstract class, especially because they can add methods to it on the fly.

Duck-typing languages, like Ruby, use the presence/absence or behavior of methods at runtime to determine whether they should be called or not. Therefore your question, as it applies to an abstract method, makes sense

def get_db_name
   raise 'this method should be overriden and return the db name'
end

and that should be about the end of the story. The only reason to use abstract classes in Java is to insist that certain methods get "filled-in" while others have their behavior in the abstract class. In a duck-typing language, the focus is on methods, not on classes/types, so you should move your worries to that level.

In your question, you're basically trying to recreate the abstract keyword from Java, which is a code-smell for doing Java in Ruby.

Yar
A: 

I did it this way, so it redefines new on child class to find a new on non abstract class. I still don't see any practical in using abstract classes in ruby.

puts 'test inheritance'
module Abstract
  def new
    throw 'abstract!'
  end
  def inherited(child)
    @abstract = true
    puts 'inherited'
    non_abstract_parent = self.superclass;
    while non_abstract_parent.instance_eval {@abstract}
      non_abstract_parent = non_abstract_parent.superclass
    end
    puts "Non abstract superclass is #{non_abstract_parent}"
    (class << child;self;end).instance_eval do
      define_method :new, non_abstract_parent.method('new')
      # # Or this can be done in this style:
      # define_method :new do |*args,&block|
        # non_abstract_parent.method('new').unbind.bind(self).call(*args,&block)
      # end
    end
  end
end

class AbstractParent
  extend Abstract
  def initialize
    puts 'parent initializer'
  end
end

class Child < AbstractParent
  def initialize
    puts 'child initializer'
    super
  end
end

# AbstractParent.new
puts Child.new

class AbstractChild < AbstractParent
  extend Abstract
end

class Child2 < AbstractChild

end
puts Child2.new
ZeusTheTrueGod