Creating an Abstract Method

Problem

You want to define a method of a class, but leave it for subclasses to fill in the actual implementations.

Solution

Define the method normally, but have it do nothing except raise a NotImplementedError:

class Shape2D def area raise NotImplementedError. new("#{self.class.name}#area is an abstract method.") end end Shape2D.new.area # NotImplementedError: Shape2D#area is an abstract method.

A subclass can redefine the method with a concrete implementation:

class Square < Shape2D def initialize(length) @length = length end def area @length ** 2 end end Square.new(10).area # => 100

 

Discussion

Ruby doesn't have a built-in notion of an abstract method or class, and though it has many built-in classes that might be considered "abstract," it doesn't enforce this abstractness the way C++ and Java do. For instance, you can instantiate an instance of Object or Numeric, even though those classes don't do anything by themselves.

In general, this is in the spirit of Ruby. But it's sometimes useful to define a superclass method that every subclass is expected to implement. The NotImplementedError error is the standard way of conveying that a method is not there, whether it's abstract or just an unimplemented stub.

Unlike other programming languages, Ruby will let you instantiate a class that defines an abstract method. You won't have any problems until you actually call the abstract method; even then, you can catch the NotImplementedError and recover. If you want, you can make an entire class abstract by making its initialize method raise a NotImplementedError. Then no one will be able to create instances of your class:[4]

[4] Of course, unless you freeze the class afterwards, someone else can reopen your class, define an empty initialize, and then create instances of your class.

class Shape2D def initialize raise NotImplementedError. new("#{self.class.name} is an abstract class.") end end Shape2D.new # NotImplementedError: Shape2D is an abstract class.

We can do the same thing in less code by defining a decorator method of Class that creates an abstract method by the given name.

class Class def abstract(*args) args.each do |method_name| define_method(method_name) do |*args| if method_name == :initialize msg = "#{self.class.name} is an abstract class." else msg = "#{self.class.name}##{method_name} is an abstract method." end raise NotImplementedError.new(msg) end end end end

Here's an abstract class that defines an abstract method move:

class Animal abstract :initialize, :move end Animal.new # NotImplementedError: Animal is an abstract class.

Here's a concrete subclass that doesn't bother to define an implementation for the abstract method:

class Sponge < Animal def initialize @type = :Sponge end end sponge = Sponge.new sponge.move # NotImplementedError: Sponge#move is an abstract method.

Here's a concrete subclass that implements the abstract method:

class Cheetah < Animal def initialize @type = :Cheetah end def move "Running!" end end Cheetah.new.move # => "Running!"

Abstract methods declared in a class are, by convention, eventually defined in the subclasses of that class. But Ruby doesn't enforce this either. An abstract method has a definition; it just happens to be one that always throws an error.

Since Ruby lets you reopen classes and redefine methods later, the definition of a concrete method can happen later in time instead of further down the inheritance tree. The Sponge class defined above didn't have a move method, but we can add one now:

class Sponge def move "Floating on ocean currents!" end end sponge.move # => "Floating on ocean currents!"

You can create an abstract singleton method, but there's not much point unless you intend to fill it in later. Unlike instance methods, singleton methods aren't inherited by subclasses. If you were to define Superclass.foo abstract, then define it for real as Subclass.foo, you would have accomplished little: Superclass.foo would still exist separately and would still be abstract.

Категории