Interfaces in Ruby

Interfaces are named collections of method signatures. In Ruby, we use modules to define interfaces, although they are not strictly enforced like in some other languages.

require 'math'

# Here's a basic interface for geometric shapes.
module Geometry
  def area
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end

  def perim
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end
end

# For our example we'll implement this interface on
# `Rect` and `Circle` classes.
class Rect
  include Geometry
  attr_reader :width, :height

  def initialize(width, height)
    @width = width
    @height = height
  end

  # To implement an interface in Ruby, we just need to
  # implement all the methods in the module. Here we
  # implement `Geometry` on `Rect`.
  def area
    @width * @height
  end

  def perim
    2 * @width + 2 * @height
  end
end

class Circle
  include Geometry
  attr_reader :radius

  def initialize(radius)
    @radius = radius
  end

  # The implementation for `Circle`.
  def area
    Math::PI * @radius * @radius
  end

  def perim
    2 * Math::PI * @radius
  end
end

# If an object includes a module, then we can call
# methods that are in the named module. Here's a
# generic `measure` method taking advantage of this
# to work on any `Geometry`.
def measure(g)
  puts g.inspect
  puts g.area
  puts g.perim
end

# Main program
r = Rect.new(3, 4)
c = Circle.new(5)

# The `Circle` and `Rect` classes both
# include the `Geometry` module so we can use
# instances of these classes as arguments to `measure`.
measure(r)
measure(c)

To run the program:

$ ruby interfaces.rb
#<Rect:0x00007f8b9a8b0a98 @width=3, @height=4>
12.0
14.0
#<Circle:0x00007f8b9a8b0a20 @radius=5>
78.53981633974483
31.41592653589793

In Ruby, we use modules to define interfaces. While Ruby doesn’t enforce interface implementation as strictly as some other languages, it’s a common practice to raise a NotImplementedError for methods that should be implemented by including classes.

The include keyword in Ruby is used to add methods from a module to a class, which is similar to implementing an interface in other languages.

Ruby’s dynamic nature allows for duck typing, which means that as long as an object responds to the methods called on it, it can be used regardless of its type. This is why we can pass both Rect and Circle instances to the measure method without explicitly declaring that they implement a specific interface.

To learn more about Ruby’s modules and how they can be used to create interfaces, check out the Ruby documentation on modules.