1. Computing

Modules: Containment and Composition

By

Modules: Containment and Composition

What is a module? You can think of modules as containers for methods and constants. This has two practical uses: implementing namespaces and sharing methods between one or more classes. While namespaces are easy to understand (almost every object oriented language has such a feature), it's unlikely you've encountered anything quite like the method sharing mechanism known as "mixin modules."

Defining Modules

To define a module, use the module keyword. Much like the class keyword, anything inside the module block will become part of a Module object which is referred to by the name in the module statement. So, if we were to evolve our previous class example slightly, we might want to remove a set of common methods from the Person class and put them into a Person module.


#!/usr/bin/env ruby

module Person
  NAME_REGEXP = /^[A-Z]+ [A-Z]+$/i

  attr_reader :name, :age
  
  def name=(name)
    if name =~ NAME_REGEXP
      @name = name
    else
      puts "Invalid name '#{name}'"
    end
  end

  def have_birthday
    @age += 1
  end

  def whoami
    puts "You are #{@name}, age #{@age}"
  end
end

This is very similar to the original class, so really what's the difference? Obviously you can't instantiate this as an object, a module is not a class. What we've done is contained all these methods (and a single constant) with the intent of using it as a "mixin" module.

Composition: Mixin Modules

Mixin modules are something like inheritance. I say they're like inheritance because they're implemented in a very similar way (and understanding how they're implemented is key to understanding how to use them). So let's take a step back and look at inheritance, the ancestor chain and method lookups once again.

Let's say we have two classes, A and B. B is a child class of A.


class A; end
class B < A; end

We have an instance of B and we call a method on it. Ruby will look upward through B's ancestor chain to find that method. B's ancestor chain looks like B -> A -> Object -> Kernel -> BasicObject, so Ruby will search the methods of B, A, Object, Kernel (which is a module and not a class, we're getting to that) and BasicObject to find the method we called.

Back to our Person mixin module. What you can do with this module is include it in a class. All methods inside the module now become instance methods of the class. In essence, when a module is included, it inserts itself into the ancestor chain just as if you had inherited from a class. The methods in the mixin module are now full-fledged instance methods of that class. This also includes access to instance variables, any instance variables accessed from methods in the mixin module will access the instance variables of the object receiving the method call.

Note that the word "include" here should not be confused with the "include" mechanism in many other languages, which will load another file. You aren't loading any other files when using the "include" keyword (that's what require is for), you're just saying that "the methods in the module should be included in the class."

So let's put our Person module to use. We'll create a few classes that are people and a class that acts like a person. This part is key, and this is where composition differs fundamentally from inheritance. With inheritance, all classes in the class hierarchy are considered to be different types of the same thing. With composition, you're describing things that act the same way, or have the same capabilities. A robot might have all the same capabilities as a person, but it is most certainly not a person.


class Professor
  # A professor is a person
  include Person

  def give_lecture
    100.times { puts "Blah, blah, blah" }
  end
end

class Student
  # Students are people too
  include Person

  def take_notes
    # Remove 100 pieces of paper from inventory
  end
end

class Robot
  # A robot (in the "I, Robot" sense of the word) is not
  # a person, but it acts like and has the capabilities
  # of a person
  include Person

  # Robots are made differently than people
  def initialize(serial_number, age)
    @name = serial_number
    @age = age
  end
end

p = Professor.new("Smart Oldman", 63)
s = Student.new("Eager Kidman", 19)
r = Robot.new(47382743, 223)

p.whoami
s.whoami
r.whoami

The Professor and Student classes both include the Person module. They both have slightly different capabilities (the professor can give lectures and the student can take notes). After the include statements, the ancestor chain is changed slightly. Instead of Professor -> Object -> Kernel -> BasicObject, it now reads Professor -> Person -> Object -> Kernel -> BasicObject. This becomes important when we get to the Robot class.

The robot, however, is a bit different. It isn't a person, but it acts like a person. For this reason, it implements its initialize method slightly differently (robots are manufactured in a decidedly different manner). This is possible because when Ruby goes to call the initialize method on the new Robot instance, the initialize method in the Robot class itself comes first in the ancestor chain.

Modules as Namespaces

The other major usage for module is that of namespaces. Especially with library code, you have to be very careful of name clashes. This is especially true in Ruby. In other languages, such as C++, if something is defined twice you get an error message. In Ruby, if a class is "defined twice," the methods from the second class statement are simply added to the already existing class. If a method is defined twice, the second definition writes over the first definition. Name clashes in Ruby are not simple errors, they often introduce very strange bugs that, at first, defy logic.

To use a module as a namespace, simply put your classes and methods inside of a module. When you wish to refer to your classes, use the scope resolution operator :: (two colons) as you would in C++ and many other languages.

If we were writing a game, we could package up our classes so they wouldn't interfere with the class names in another library.


#!/usr/bin/env ruby

module Game
  class Game; end

  class Player
    def initialize
      @e = Enemy.new
    end
  end

  class Enemy
  end
end

There are a few subtleties here. First, from outside of the module you can refer to any of these classes as Game::Game, Game::Player or Game::Enemy. Note that we have two constants called Game, but the scope resolution operator makes sure we refer to the correct one.

From inside the module, the scope resolution operator doesn't need to be used. Any class names (or other constants) are first looked for in the current module. So within the Player class, you can refer to the Enemy class without any scope resolution operator. The classes don't even need to be aware they're inside of a namespace module.

But what if we access the Game constant? Does it access the module, or the class? It will access the class, accessing the module is a bit more complicated. We need to use the global scope in the scope resolution operator. To do this, we put an extra :: operator at the beginning of the expression. If you want to refer to the Game module, you would say ::Game. If you wanted to refer to the classes inside that module explicitly, you could say ::Game::Player or ::Game::Enemy.

And of course, modules can contain other modules. To refer to the Util module, you can say Game::Util. To call any of the methods in that module, you can say Game::Util::start_game, or from within the Game module itself you can say Util::start_game. One final thing to note is that methods in namespace modules should be class methods, and not instance methods. So the start_game method is defined as def self.start_game.

  1. About.com
  2. Computing
  3. Ruby
  4. Beginning Ruby
  5. Object Oriented Programming
  6. Modules: Containment and Composition

©2014 About.com. All rights reserved.