Modules

Like a class, a module is a named group of methods, constants, and class variables. Modules are defined much like classes are, but the module keyword is used in place of the class keyword. Unlike a class, however, a module cannot be instantiated, and it cannot be subclassed. Modules stand alone; there is no “module hierarchy” of inheritance.

Modules are used as namespaces and as mixins. The subsections that follow explain these two uses.

Just as a class object is an instance of the Class class, a module object is an instance of the Module class. Class is a subclass of Module. This means that all classes are modules, but not all modules are classes. Classes can be used as namespaces, just as modules can. Classes cannot, however, be used as mixins.

Modules as Namespaces

Modules are a good way to group related methods when object-oriented programming is not necessary. Suppose, for example, you were writing methods to encode and decode binary data to and from text using the Base64 encoding. There is no need for special encoder and decoder objects, so there is no reason to define a class here. All we need are two methods: one to encode and one to decode. We could define just two global methods:

def base64_encode
end

def base64_decode
end

To prevent namespace collisions with other encoding and decoding methods, we’ve given our method names the base64 prefix. This solution works, but most programmers prefer to avoid adding methods to the global namespace when possible. A better solution, therefore, is to define the two methods within a Base64 module:

module Base64
  def self.encode
  end

  def self.decode
  end
end

Note that we define our methods with a self. prefix, which makes them “class methods” of the module. We could also explicitly reuse the module name and define the methods like this:

module Base64
  def Base64.encode
  end

  def Base64.decode
  end
end

Defining the methods this way is more repetitive, but it more closely mirrors the invocation syntax of these methods:

# This is how we invoke the methods of the Base64 module
text = Base64.encode(data)
data = Base64.decode(text)

Note that module names must begin with a capital letter, just as class names do. Defining a module creates a constant with the same name as the module. The value of this constant is the Module object that represents the module.

Modules may also contain constants. Our Base64 implementation would likely use a constant to hold a string of the 64 characters used as digits in Base64:

module Base64
  DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 
           'abcdefghijklmnopqrstuvwxyz' 
           '0123456789+/'
end

Outside the Base64 module, this constant can be referred to as Base64::DIGITS. Inside the module, our encode and decode methods can refer to it by its simple name DIGITS. If the two methods had some need to share nonconstant data, they could use a class variable (with a @@ prefix), just as they could if they were defined in a class.

Nested namespaces

Modules, including classes, may be nested. This creates nested namespaces but has no other effect: a class or module nested within another has no special access to the class or module it is nested within. To continue with our Base64 example, let’s suppose that we wanted to define special classes for encoding and decoding. Because the Encoder and Decoder classes are still related to each other, we’ll nest them within a module:

module Base64
  DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

  class Encoder
    def encode
    end
  end

  class Decoder
    def decode
    end
  end

  # A utility function for use by both classes
  def Base64.helper
  end
end

By structuring our code this way, we’ve defined two new classes, Base64::Encoder and Base64::Decoder. Inside the Base64 module, the two classes can refer to each other by their unqualified names, without the Base64 prefix. And each of the classes can use the DIGITS constant without a prefix.

On the other hand, consider the Base64.helper utility function. The nested Encoder and Decoder classes have no special access to the methods of the containing module, and they must refer to this helper method by its fully qualified name: Base64.helper.

Because classes are modules, they too can be nested. Nesting one class within another only affects the namespace of the inner class; it does not give that class any special access to the methods or variables of the outer class. If your implementation of a class requires a helper class, a proxy class, or some other class that is not part of a public API, you may want to consider nesting that internal class within the class that uses it. This keeps the namespace tidy but does not actually make the nested class private in any way.

See Constant Lookup for an explanation of how constant names are resolved when modules are nested.

Modules As Mixins

The second use of modules is more powerful than the first. If a module defines instance methods instead of the class methods, those instance methods can be mixed in to other classes. Enumerable and Comparable are well-known examples of mixin modules. Enumerable defines useful iterators that are implemented in terms of an each iterator. Enumerable doesn’t define the each method itself, but any class that defines it can mix in the Enumerable module to instantly add many useful iterators. Comparable is similar; it defines comparison operators in terms of the general-purpose comparator <=>. If your class defines <=>, you can mix in Comparable to get <, <=, == >, >=, and between? for free.

To mix a module into a class, use include. include is usually used as if it were a language keyword:

class Point
  include Comparable
end

In fact, it is a private instance method of Module, implicitly invoked on self—the class into which the module is being included. In method form, this code would be:

class Point
  include(Comparable)
end

Because include is a private method, it must be invoked as a function, and we cannot write self.include(Comparable). The include method accepts any number of Module objects to mix in, so a class that defines each and <=> might include the line:

include Enumerable, Comparable

The inclusion of a module affects the type-checking method is_a? and the switch-equality operator ===. For example, String mixes in the Comparable module and, in Ruby 1.8, also mixes in the Enumerable module:

"text".is_a? Comparable         # => true
Enumerable === "text"           # => true in Ruby 1.8, false in 1.9

Note that instance_of? only checks the class of its receiver, not superclasses or modules, so the following is false:

"text".instance_of? Comparable  # => false

Although every class is a module, the include method does not allow a class to be included within another class. The arguments to include must be modules declared with module, not classes.

It is legal, however, to include one module into another. Doing this simply makes the instance methods of the included modules into instance methods of the including module. As an example, consider this code from Chapter 5:

module Iterable       # Classes that define next can include this module
  include Enumerable          # Define iterators on top of each
  def each                    # And define each on top of next
    loop { yield self.next }
  end
end

The normal way to mix in a module is with the Module.include method. Another way is with Object.extend. This method makes the instance methods of the specified module or modules into singleton methods of the receiver object. (And if the receiver object is a Class instance, then the methods of the receiver become class methods of that class.) Here is an example:

countdown = Object.new       # A plain old object
def countdown.each           # The each iterator as a singleton method
  yield 3
  yield 2
  yield 1
end
countdown.extend(Enumerable) # Now the object has all Enumerable methods  
print countdown.sort         # Prints "[1, 2, 3]"

Includable Namespace Modules

It is possible to define modules that define a namespace but still allow their methods to be mixed in. The Math module works like this:

Math.sin(0)    # => 0.0: Math is a namespace 
include Math   # The Math namespace can be included
sin(0)         # => 0.0: Now we have easy access to the functions

The Kernel module also works like this: we can invoke its methods through the Kernel namespace, or as private methods of Object, into which it is included.

If you want to create a module like Math or Kernel, define your methods as instance methods of the module. Then use module_function to convert those methods to “module functions.” module_function is a private instance method of Module, much like the public, protected, and private methods. It accepts any number of method names (as symbols or strings) as arguments. The primary effect of calling module_function is that it makes class method copies of the specified methods. A secondary effect is that it makes the instance methods private (we’ll have more to say about this shortly).

Like the public, protected, and private methods, the module_function method can also be invoked with no arguments. When invoked in this way, any instance methods subsequently defined in the module will be module functions: they will become public class methods and private instance methods. Once you have invoked module_function with no arguments, it remains in effect for the rest of the module definition—so if you want to define methods that are not module functions, define those first.

It may seem surprising at first that module_function makes the instance methods of a module private. The reason to do this is not really for access control, as obviously the methods are also available publicly through the module’s namespace. Instead, the methods are made private to restrict them to function-style invocation without an explicit receiver. (The reason that these are called module functions instead of module methods is that they must be invoked in functional style.) Forcing included module functions to be invoked without a receiver makes it less likely that they’ll be mistaken for true instance methods. Suppose we’re defining a class whose methods perform a lot of trigonometry. For our own convenience, we include the Math module. Then we can invoke the sin method as a function instead of calling Math.sin. The sin method is implicitly invoked on self, but we don’t actually expect it to do anything to self.

When defining a module function, you should avoid using self, because the value of self will depend on how it is invoked. It is certainly possible to define a module function that behaves differently depending on how it is invoked. But if you are going to do that, then it makes more sense to simply define one class method and one instance method.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.10.162