Abstract Method in Ruby
Abstract methods are supposed to be less useful in Ruby because it's not strongly statically typed.
However, this is what I do:
class AbstractThing
MESS = "SYSTEM ERROR: method missing"
def method_one; raise MESS; end
def method_two; raise MESS; end
end
class ConcreteThing < AbstractThing
def method_one
puts "hi"
end
end
a = ConcreteThing.new
a.method_two # -> raises error.
It rarely seems to be necessary, however.
I like the answer by pvandenberk, but I would improve it as follows:
module Canine # in Ruby, abstract classes are known as modules
def bark
fail NotImplementedError, "A canine class must be able to #bark!"
end
end
Now if you make a class belonging to Canine
"abstract class" (ie. a class that has Canine
module in its ancestors), it will complain if it is found that #bark
method is not implemented:
class Dog
include Canine # make dog belong to Canine "abstract class"
end
Dog.new.bark # complains about #bark not being implemented
class Dog
def bark; "Bow wow!" end
end
# Now it's OK:
Dog.new.bark #=> "Bow wow!"
Note that since Ruby classes are not static, but always open to changes, Dog
class itself cannot enforce existence of #bark
methods, since it doesn't know when is it supposed to be finished. If you as a programmer do, it is up to you to test it at such time.
My preferred approach is similar but slightly different... I prefer it as follows, because it makes the code self-documenting, giving you something very similar to Smalltalk:
class AbstractThing
def method_one; raise "SubclassResponsibility" ; end
def method_two; raise "SubclassResponsibility" ; end
def non_abstract_method; method_one || method_two ; end
end
Some people will complain that this is less DRY, and insist on creating an exception subclass and/or put the "SubclassResponsibility"
string in a constant, but IMHO you can dry things up to the point of being chafed, and that is not usually a good thing. E.g. if you have multiple abstract classes across your code base, where would you define the MESS
string constant?!?
I like the use of a gem like abstract_method which gives a dsl rails style syntax abstract methods:
class AbstractClass
abstract_method :foo
end
class AbstractModule
abstract_method :bar
end
class ConcreteClass < AbstractClass
def foo
42
end
end
This code will not let you load the class if the methods 'foo', 'bar' and 'mate' are not defined in the inherited class.
It does not account for classes being defined across many files, but lets get honest do many of us actually define class methods across many files? I mean if you don't count mix-ins. (which this does account for)
def self.abstract(*methods_array)
@@must_abstract ||= []
@@must_abstract = Array(methods_array)
end
def self.inherited(child)
trace = TracePoint.new(:end) do |tp|
if tp.self == child #modules also trace end we only care about the class end
trace.disable
missing = ( Array(@@must_abstract) - child.instance_methods(false) )
raise NotImplementedError, "#{child} must implement the following method(s) #{missing}" if missing.present?
end
end
trace.enable
end
abstract :foo
abstract :bar, :mate