Ruby Notes 6 Classes and Modules
Classes can include—or inherit methods fom—modules.
Ruby’s objects are strictly encapsulated: their state can be access only through the methods they define.
Any Ruby program can add methods to existing classes, and it is even possible to add “singleton methods” to individual objects.
Classes
Defining a Simple Class
- Accessors and Attributes
-
metaprogramming
-
attr
,attr_reader
, andattr_accessor
The
attr_reader
andattr_accessor
methods are defined byModule
class. Both methods take any number of symbols naming attributes.attr_reader
creates trivial getter methods for the instance variables with the same name.attr_accessor
creates getter and setter methods.class Point attr_accessor :x, :y # Define accessor methods for our instance variables end class Point attr_reader :x, :y # Define reader methods for our instance variable # attr_reader "x", "y" Equivalently. end
attr
is a similar method with a shorted name as a synonym forattr_reader
.attr :x # Define a trivial getter method x for @x attr :y, true # Define getter and setter methods for @y
The
attr
,attr_reader
, andattr_accessor
methods create instance method for us (metaprogramming).
-
Method Visibility: Public, Protected, Private
Methods are normally public unless they are explicitly declared to be private or protected.
The initialize
method is always implicitly private.
Global funcitons are defined as private instance methods of Object
.
A private method is internal to the implementation of a class, and it can only be called by other instance method of the class.
Private methods are implicityly invoked on self
, and may not be explicitly invoked on an object.
A protected method is like a private method in that it can only be invoked from within the implementation of a calss or its subclasses.
Protected methods may be explicitly invoked on any instance of the class, and it is not restricted to implicit invocation on self
.
Protected methods are the least commonly defined and also the most difficult to understand.
Method visibility is declared with three methods named public
, private
, and protected
. These are instance method of the Module
class.
All classes are modules, and inside a class definition (both outside method definitions), self
refers to class being defined.
Instance and class variables are encapsulated and effectively private, and constants are effectively public.
There is no way to make an instance variable accessible from outside a class. And there is no way to define a constant that is inaccessible to outside use.
-
private_class_method
andpublic_class_method
You can make a private class method private with
private_class_method
.private_class_method :new
You can make a private class method public again with
public_class_method
.
Subclassing and inheritance
In Ruby 1.9, Object
is no longer the root of the class hierarchy. A new class named BasicObject
serves that purpose, and Object
is a subclass of BasicObject
. BasicObject
is a very simple class, with almost no methods of its own, and is useful as the superclass of delegating wrapper classes.
When you create a class in Ruby 1.9, you still extend Object
unless you explicity specify the superclass, and most programmer will need to use or extend BasicObject
.
The syntax for extending a class is to add < character and the name of the superclass to your class
statement.
class Point3D < Point # Define class Point3D as a subclass of Point
end
Subclassing a Struct
class Point3D < Struct.new("Point3D", :x, :y, :z)
# Superclass struct gives us acess method, ==, to_s, etc
# Add point-specific methods here
end
-
Overriding Methods
# Greet the World class WorldGreeter def greet # Display a greeting puts "#{greeting} #{who}" end def greeting # What greeting to use "Hello" end def who # Who to greet "World" end end # Greet the world in Spanish class SpanishWorldGreeter < WorldGreeter def greeting # Override the greeting "Hola" end end # We call a mehtod defined in WorldGreeter, which calls the overriden # version of greeting in SpanishWorldGreeter, and prints "Hola World" SpanishWorldGreeter.new.greet
-
abstract vs concrete
# This class is abstract; it doesn't define greeting or who # No special syntax is required: any class that invokes methods that are # intended for a subclass to implement is abstract. class AbstractGreeter def greet puts "#{greeting} #{who}" end end # A concrete subclass class WorldGreeter < AbstractGreeter def greeting; "Hello"; end def who; "World"; end end WorldGreeter.new.greet # Displays "Hello World"
-
Overriding private methods
Privates methods are inherited by subclass.
Subclasses can invoke and override private methods.
You should only subclass when you are familiar with the implementation of the superclass.
Compositition > Inheritance
-
super
super
works like a special method invocation: it invokes a method with the same name as the current one, in the superclass of the current class.If you use
super
as a bare keyword—with no arguments and no parentheses—then all of the arguments that were passed to the current method are passed to the superclass method.class Point3D < Point def initialize(x,y,z) # Pass our first two arguments along to the superclass initialize method super(x , y) # And deal with the third argument ourself @z = z; end end
-
-
Inheritance of Class Methods
Class methods may be inherited and overriden just as instance methods can be.
As a stylistic matter, it is preferable to invoke class methods through the class object on which they are defined.
Class methods can use
super
just as instance methods can to invoke the same-named method in the superclass.class Point def self.sum(*args) s = 0 args.each { |x| s += x } s end end class Point3D < Point def self.sum(*args) puts "Point3D" super(*args) end end
-
Inheritance and Instance Variables
Inheritance variables often appear to be inherited in Ruby.
class Point3D < Point def initialize(x,y,z) super(x , y) @z = z; end def to_s "(#@x, #@y, #@z)" # Variables @x and @y inherited? end end
-
Inheritance and Class Variables
Class variables are shared by a class and all of its subclasses.
-
Inheritance of Constants
Constants are inherited can can be overriden, much like instance methods can.
class Point ORGIN = Point.new 0, 0 def initialize(x, y) @x, @y = x, y end end class Point3D ORGIN = Point3D.new 0, 0, 0 def initialize(x, y, z) super x, y @z = z end end
Object Creation and Intialization
-
new
,allocate
, andintialize
Every class inerits the class method
new
.new
method has two jobs: it mustallocate
a new object—actually bring the object into existence—and it mustintialize
the object.The
new
metho would look something like this:def new(*args) o = self.allocate # Create a new object of this class o.intialize(*args) # Call the object's intialize method with our args o # Return new object; ignore return value of intialize end
-
allocate
allocate
is an instance method ofClass
, and it is inherited by all class objects.Its purpose is to create a new instance of the class.
You can call it yourself to create uninitialized instances of a class.
But don’t try to override it; Ruby always inovkes this mehtod directly, ignoring any overriding versions you may have defined.
-
initalize
initialze
is an instance method and usually to create instance variables for the obect and set them to their initial values.Ruby implicitly makes the
initialize
method private.-
Class::new and Class#new
The class method
Class::new
is theClass
class’ own version of the method, and it can be used to create new classes.
-
-
-
Factory Methods
class Point # Define an initialize method as usual... def initialize(x,y) # Expects Cartesian coordinates @x,@y = x,y end # But make the factory method new private private_class_method :new def Point.cartesian(x,y) # Factory method for Cartesian coordinates new(x,y) # We can still call new from other class methods end def Point.polar(r, theta) # Factory method for polar coordinates new(r*Math.cos(theta), r*Math.sin(theta)) end end
-
dup
,clone
, andintialize_copy
-
marshal_dump and marshal_load
-
The Singleton Pattern
require 'singleton' # Singleton module is not built-in class PointSats # Define a class include Singleton # Make it as singleton def intialize # A normal initialization method @n, @totalX, @totalY = 0, 0.0, 0.0 end def record(point) # Record a new point @n += 1 @totalX += point.x @totalY += point.y end def report # Report point statistics puts "Number of points created: #@n" puts "Average X coordinate: #{@totalX/@n}" puts "Average Y coordinate: #{@totalY/@n}" end end class Point def initialize(x,y) @x, @y = x, y PointSats.instance.record(self) end end
The
Singleton
module automatically creates theinstance
class method for us.PointSats.instance.report
Modules
Like a class, a module is a named group of methods, constants, and class variables.
Modules stand alone; there is no “module hierarchy” of ineritance.
Modules are used as namespaces and as mixins.
Modules as Namespaces
Modules are a good way to group related methods when object-oriented programming is not necessary.
def base64_encode
end
def base64_decode
end
To define the two methods within a Base64
module to prevent namespace collisions.
module Base64 # Note the module names must begin with a capital letter.
def self.encode # def Base64.encode
end
def self.decode # def Base64.decode
end
Modules may also contain constants.
module Base64
DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' \
'abcdefghijklmnopqrstuvwxyz' \
' 0123456789+/ '
end
Outside the Base64
module, this constant can be referred to as Base64::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 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.
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
andBase64::Decoder
.Inside the
Base64
module, the two classes can refer to each other by their unqualified names, without theBase64
prefix.And each of the classes can use the
DIGITS
constant without a prefix. -
Modules As Mixins
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
mehtod itself, but any class that defines it can mix in the Enumerable module to instantly add many useful iterators.
And class that defines <=>
can be mixed in Comparable
to get <
,<=
,==
,>=
, and between?
for free.
To mix a module into a class, use include
.
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.
The inclusion of a module affects the type-checking method is_a?
and the switch-equality operator ===
.
"text".is_a? Comparable # => true
Enumerable === "text" # => true in Ruby 1.8, false in 1.9
Note the instance_of?
only checks the class of its receiver, not superclasses or modules, so the following is false.
"text".instance_of? Comparable # => false
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.)
countdown = Object.new # A plain old object
def countdown.each # The each iterator as a singleton mehtod
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.
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 method 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_funciton 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.
Like the public, protected, and private methods, the module_funciton mehtod can also be invoked with no arguments. When invoked in this way, any instance methods subsequently defined in the module will be modue funciton: they will become public class methods and private instance methods.
Loading and Requiring Modules
Ruby programs may be broken up into multiple files, and the most natural way to partition a program is to place each nontrivial class or module into a separate file.
These separate files can then be reassembled into a single program (and, if well-designed, can be reused by other programs) using require or load which are global functions defined in Kernel, but are used like language keywords.
If the file to load is specified with an absolute path, or is relative to ~ (the user’s home directory), then that specific file is loaded.
Usually, however, the file is specified as a relative path, and load and require search for it relative to the directories of Ruby’s load path.
-
load
,require
andrequire_relative
-
In addition to loading source code, require can also load binary extensions to Ruby.
Binary extensions are, of course, implementation-dependent, but in C-based implementations, they typically take the form of shared library files with extension like .so or .dll.
-
load expects a complete filename including an extension.
require is usually passed a library name, with no extension, rather than a filename. In that case, it searchs for a file that has the library name as its base name and an approriate source or native library extension. If a direcotry contains both an .rb source file and a binary extension file, require will load the source file instead of the binary file.
-
load can load the same file multiple times.
require tries to prevent multiple loads of the same file.
require keeps track of the files that have been loaded by appending them to the global array
$"
(also known as $LOAD_FEATURES). -
load loads the specified file at the current $SAFE level.
require loads the specified library with $SAFE set to 0, even if the code that called require has a higher value for that variable.
-
require_relative, a special version of require, searchs and loads file from the current direcotry.
-
-
The Load Path
Ruby’s load path is an array that you can access using either of the global variables
$LOAD_PATH
or$:
.Each element of the array is the name of a direcotry that Ruby will search for files to load.
Direcotories at the start of the array are searched before direcotories at the end of the array.
The elements of $LOAD_PATH must be strings in Ruby 1.8, but in Ruby 1.9, they may be strings or any object that has a
to_path
method that returns a string. -
Executing Loaded Code
load and require execute the code in the specified file immediately.
Files loaded with load or require are executed in a new top-level scope that is different from the one in which load or require was invoked.
The loaded file can see all global variables and constants that have been defined at the time it is loaded, but it does not have access to the local scope from which the load was initiated.
-
The local variables defined in the scope from which load or require is invoked are not visible to the loaded file.
-
Any local variables created by the loaded file are discarded once the load is complete; they are never visible outside the file in which they are defined.
-
At the start of the loaded file, the value of self is always the main object, just as it is when the Ruby interpreter starts running.
-
The current module nesting is ignored within the loaded file. You cannot, for example, open a class and then load a file of method definitions. The file will be processed in a top-level socpe, not inside any class or module.
-
-
Autoloading Modules
The autoload methods of Kernel and Module allow lazy loading of files on an as-needed basis. The global autoload function allows you to register the name of an undefined constant (typically a class or module name) and a name of the library that defines it. When that constant is first referenced, the named library is loaded using require. For example:
# Require 'socket' if and when the TCPSocket is first used autoload :TCPSocket, "socket"
The Module class defines its own version of autoload to work with constants nested within another module.
Singleton Methods and the Eigenclass
To define a singleton method sum on an object Point, we’d write:
def Point.sum
# Method body goes here
end
The singleton methods of an object are instance methods of the anonymous eigenchass associated with that object.
“Eigen” is a German word meaning (roughly) “self”, “own”, “particular to,” or “characteristic of.”
The eigenclass is also called the singleton class or the metaclass.
Ruby defines a syntax for opening the eigenclass of an object and adding methods to it thats provides an alternative to defining singleton methods one by one.
To open the eigenclass of the object o, use class « o. For example, we can define class methods of Point like this:
class << Point
def class_method1 # This is an instance method of the eigenclass.
end
def class_method2
end
end
Method Lookup
When Ruby evaluates a method invocation expression, it must first figure out which method is to be invoked. The process for doing this is called method lookup or method name resolution.
For the method invocation expression o.m
, Ruby performs name resolution with the following steps:
-
First, it checks the eigenclass of o for singleton methods named m.
-
If no method m is found in the eigenclass, Ruby searchs the class of o for an instance method named m.
-
If no method m is found in the class, Ruby searchs the instance methods of any modules included by the class of o. If that class includes more than one module, then the most recently included module is searched first.
-
If no instance method m is found in the class of o or in its modules, then the search moves up the inheritance hierarchy to the superclass.
-
If no method named m is found after completing the search, then a method named method_missing is invoked instead. In order to find an appropriate definitions of this method, the name resolution algorith starts over at step 1.
The Kernel module provides a default implementation of method_missing. What it does is raise an exception of NoMethodError.
The name resolution algorithm for class methods is exactly the same as it is for instance method.
Constant Lookup
When a constant is referenced without any qualifying namespace, the Ruby interpreter must find the appropriate definition of the constant.
-
Ruby first attempts to resolve a constant reference in the lexical scope of the reference.
This means that it first checks the class or module that encloses the constant reference to see if that class or module defines the constant. If not, it checks the next enclosing class or module.
This continues untils there are no more enclosing classes or modules.
Note that top-level or “global” constants are not considered part of the lexical scope and are not considered during this part of constant lookup.
The class method Module.nesting returns the list of clases and modules that are searched in this step, in the order they are searched.
-
If no constant definition is foud in the lexically enclosing scope, Ruby next tries to resolve the constant in the inheritance hierarchy by checking the ancesstors of the class or module that referred to the constant.
The ancestors method of the containing class or module returns the list of classes and modules searched in this step.
-
If no constant definition is found in the inheritance hierarchy, then top-level constant definitions are checked.
-
If no definition can be found for the desired constant, then the const_missing method—if there is one—of the containing class or module is called and given the opportunity to provide a value for the constant.
A few points about this constant lookup algorithm.
-
Constants defined in enclosing modules are found in preference to constants defined in included modules.
-
The modules included by a class are searched before the superclass of the class.
-
The Object class is part of the inheritance hierarchy of all classes.
-
The Kernel module is an ancestor of Object.