Section 1.4. Dynamic Aspects of Ruby

1.3. OOP in Ruby

Ruby has all the elements more generally associated with OOP languages, such as objects with encapsulation and data hiding, methods with polymorphism and overriding, and classes with hierarchy and inheritance. It goes further and adds limited metaclass features, singleton methods, modules, and mixins.

Similar concepts are known by other names in other OOP languages, but concepts of the same name may have subtle differences from one language to another. This section elaborates on the Ruby understanding and usage of these elements of OOP.

1.3.1. Objects

In Ruby, all numbers, strings, arrays, regular expressions, and many other entities are actually objects. Work is done by executing the methods belonging to the object:

3.succ                 # 4 "abc".upcase           # "ABC" [2,1,5,3,4].sort       # [1,2,3,4,5] someObject.someMethod  # some result

In Ruby, every object is an instance of some class; the class contains the implementation of the methods:

"abc".class        # String "abc".class.class  # Class

In addition to encapsulating its own attributes and operations, an object in Ruby has an identity:

"abc".object_id   # 53744407

This object ID is usually of limited usefulness to the programmer.

1.3.2. Built-in Classes

More than 30 built-in classes are predefined in the Ruby class hierarchy. Like many other OOP languages, Ruby does not allow multiple inheritance, but that does not necessarily make it any less powerful. Modern OO languages frequently follow the single inheritance model. Ruby does support modules and mixins, which are discussed in the next section. It also implements object IDs, as we just saw, which support the implementation of persistent, distributed, and relocatable objects.

To create an object from an existing class, the new method is typically used:

myFile ="textfile.txt","w") myString ="This is a string object")

This is not always explicitly required, however. In particular, you would not normally bother to call new for a String object as in the previous example:

yourString = "This is also a string object" aNumber = 5  # new not needed here, either

Variables are used to hold references to objects. As previously mentioned, variables themselves have no type, nor are they objects themselves; they are simply references to objects.

x = "abc"

An exception to this is that small immutable objects of some built-in classes, such as Fixnum, are copied directly into the variables that refer to them. (These objects are no bigger than pointers, and it is more efficient to deal with them in this way.) In this case assignment makes a copy of the object, and the heap is not used.

Variable assignment causes object references to be shared.

y = "abc" x = y x         # "abc"

After executing x = y, variables x and y both refer to the same object:

x.object_id     # 53732208 y.object_id     # 53732208

If the object is mutable, a modification done to one variable will be reflected in the other:

x.gsub!(/a/,"x") y                 # "xbc"

Reassigning one of these variables has no effect on the other, however:

# Continuing previous example... x = "abc" y                 # still has value "xbc"

A mutable object can be made immutable using the freeze method:

x.freeze x.gsub!(/b/,"y")  # Error!

A symbol in Ruby refers to a variable by name rather than by reference. In many cases, it may not refer to an identifier at all but rather acts like a kind of immutable string. A symbol can be converted to a string with the to_s method.

Hearts   = :Hearts   # This is one way of assigning Clubs    = :Clubs    # unique values to constants, Diamonds = :Diamonds # somewhat like an enumeration Spades   = :Spades   # in Pascal or C. puts Hearts.to_s     # Prints "Hearts"

The "enumeration" trick we reference here probably made more sense in the earlier days of Ruby, when there was no Symbol class, and applying the colon to an identifier yielded a simple integer. If you do use this trick, don't rely on the actual symbol values being regular or predictable; just use them as constants whose value is irrelevant.

1.3.3. Modules and Mixins

Many built-in methods are available from class ancestors. Of special note are the Kernel methods mixed-in to the Object superclass; because Object is universally available, the methods added to it from Kernel are also universally available. These methods form an important part of Ruby.

The terms module and mixin are nearly synonymous. A module is a collection of methods and constants that is external to the Ruby program. It can be used simply for namespace management, but the most common use of a module is to have its features "mixed" into a class (by using include). In this case, it is used as a mixin.

This term was apparently borrowed most directly from Python. (It is sometimes written as mix-in; but we write it as a single word.) It is worth noting that some LISP variants have had this feature for more than two decades.

Do not confuse this usage of the term module with another usage common in computing. A Ruby module is not an external source or binary file (though it may be stored in one of these). A Ruby module instead is an OOP abstraction similar to a class.

An example of using a module for namespace management is the frequent use of the Math module. To use the definition of pi, for example, it is not necessary to include the Math module; you can simply use Math::PI as the constant.

A mixin is a way of getting some of the benefits of multiple inheritance without dealing with all the difficulties. It can be considered a restricted form of multiple inheritance, but the language creator Matz has called it single inheritance with implementation sharing.

Note that include appends features of a namespace (a module) to the current space; the extend method appends functions of a module to an object. With include, the module's methods become available as instance methods; with extend, they become available as class methods.

We should mention that load and require do not really relate to modules but rather to nonmodule Ruby sources and binaries (statically or dynamically loadable). A load operation essentially reads a file and inserts it at the current point in the source file so that its definitions become available at that point. A require operation is similar to a load, but it will not load a file if it has already been loaded.

The Ruby novice, especially from a C background, may be tripped up by require and include, which are basically unrelated to each other. You may easily find yourself doing a require followed by an include to use some externally stored module.

1.3.4. Creating Classes

Ruby has numerous built-in classes, and additional classes may be defined in a Ruby program. To define a new class, the following construct is used:

class ClassName # ... end

The name of the class is itself a global constant and thus must begin with an uppercase letter. The class definition can contain class constants, class variables, class methods, instance variables, and instance methods. Class data are available to all objects of the class, whereas instance data are available only to the one object.

By the way: Classes in Ruby do not strictly speaking have names. The "name" of a class is only a constant that is a reference to an object of type Class (since in Ruby, Class is a class). There can certainly be more than one constant referring to a class, and these can be assigned to variables just as we can with any other object (since in Ruby, Class is an object). If all this confuses you, don't worry about it. For the sake of convenience, the novice can think of a Ruby class name as being like a C++ class name.

Here we define a simple class.

class Friend   @@myname = "Fred" # a class variable   def initialize(name, sex, phone)     @name, @sex, @phone = name, sex, phone     # These are instance variables   end   def hello   # an instance method     puts "Hi, I'm #{@name}."   end   def Friend.our_common_friend   # a class method     puts "We are all friends of #{@@myname}."   end end f1 ="Susan","F","555-0123") f2 ="Tom","M","555-4567") f1.hello                  # Hi, I'm Susan. f2.hello                  # Hi, I'm Tom. Friend.our_common_friend  # We are all friends of Fred.

Since class-level data is accessible throughout the class, it can be initialized at the time the class is defined. If a method named initialize is defined, it is guaranteed to be executed right after an instance is allocated. The initialize is similar to the traditional concept of a constructor, but it does not have to handle memory allocation. Allocation is handled internally by new, and deallocation is handled transparently by the garbage collector.

Now consider this fragment, and pay attention to the getmyvar, setmyvar, and myvar= methods:

class MyClass   NAME = "Class Name" # class constant   @@count = 0 #  Initialize a class variable   def initialize # called when object is allocated     @@count += 1     @myvar = 10   end   def MyClass.getcount # class method     @@count # class variable   end   def getcount # instance returns class variable!     @@count # class variable   end   def getmyvar # instance method     @myvar # instance variable   end   def setmyvar(val) # instance method sets @myvar     @myvar = val   end   def myvar=(val) # Another way to set @myvar     @myvar = val   end end foo = # @myvar is 10 foo.setmyvar 20 # @myvar is 20 foo.myvar = 30 # @myvar is 30

Here we see that getmyvar returns the value of @myvar, and setmyvar sets it. (In the terminology of many programmers, these would be referred to as a getter and a setter.) These work fine, but they do not exemplify the Ruby way of doing things. The method myvar= looks like assignment overloading (though strictly speaking, it isn't); it is a better replacement for setmyvar, but there is a better way yet.

The class called Module contains methods called attr, attr_accessor, attr_reader, and attr_writer. These can be used (with symbols as parameters) to automatically handle controlled access to the instance data. For example, the three methods getmyvar, setmyvar, and myvar= can be replaced by a single line in the class definition:

attr_accessor :myvar

This creates a method myvar that returns the value of @myvar and a method myvar= that enables the setting of the same variable. Methods attr_reader and attr_writer create read-only and write-only versions of an attribute, respectively.

Within the instance methods of a class, the pseudovariable self can be used as needed. This is only a reference to the current receiver, the object on which the instance method is invoked.

The modifying methods private, protected, and public can be used to control the visibility of methods in a class. (Instance variables are always private and inaccessible from outside the class except by means of accessors.) Each of these modifiers takes a symbol like :foo as a parameter; if this is omitted, the modifier applies to all subsequent definitions in the class. For example:

class MyClass   def method1   # ...   end   def method2   # ...   end   def method3   # ...   end   private :method1   public   :method2   protected :method3   private   def my_method   # ...   end   def another_method   # ...   end end

In the preceding class, method1 will be private, method2 will be public, and method3 will be protected. Because of the private method with no parameters, both my_method and another_method will be private.

The public access level is self-explanatory; there are no restrictions on access or visibility. The private level means that the method is accessible only within the class or its subclasses, and it is callable only in "function form"with self, implicit or explicit, as a receiver. The protected level means that a method is callable only from within its class, but unlike a private method, it can be called with a receiver other than self.

The default visibility for the methods defined in a class is public. The exception is the instance initializing method initialize. Methods defined at the top level are also public by default; if they are private, they can be called only in function form (as, for example, the methods defined in Object).

Ruby classes are themselves objects, being instances of the metaclass Class. Ruby classes are always concrete; there are no abstract classes. However, it is theoretically possible to implement abstract classes in Ruby if you really want to do so.

The class Object is at the root of the hierarchy. It provides all the methods defined in the built-in Kernel module.

To create a class that inherits from another class, define it in this way:

class MyClass < OtherClass   # ... end

In addition to using built-in methods, it is only natural to define your own and also to redefine and override existing ones. When you define a method with the same name as an existing one, the previous method is overridden. If a method needs to call the "parent" method that it overrides (a frequent occurrence), the keyword super can be used for this purpose.

Operator overloading is not strictly an OOP feature, but it is familiar to C++ programmers and certain others. Because most operators in Ruby are simply methods anyway, it should come as no surprise that these operators can be overridden or defined for user-defined classes. Overriding the meaning of an operator for an existing class may be rare, but it is common to want to define operators for new classes.

It is possible to create aliases or synonyms for methods. The syntax (used inside a class definition) is as follows:

alias newname oldname

The number of parameters will be the same as for the old name, and it will be called in the same way. Note that there is no comma here; alias is not a method name, but a keyword. There is a method called alias_method, which behaves similarly; like all other methods, its parameters do require separation by commas.

1.3.5. Methods and Attributes

As we've seen, methods are typically used with simple class instances and variables by separating the receiver from the method with a period (receiver.method). In the case of method names that are punctuation, the period is omitted. Methods can take arguments:

Time.mktime(2000, "Aug", 24, 16, 0)

Because every expression returns a value, method calls may typically be chained or stacked:

3.succ.to_s /(x.z).*?(x.z).*?/.match("x1z_1a3_x2z_1b3_").to_a[1..3] 3+2.succ

Note that there can be problems if the cumulative expression is of a type that does not support that particular method. Specifically, some methods return nil under certain conditions, and this usually causes any methods tacked onto that result to fail. (Of course, nil is an object in its own right, but it will not have all the same methods that, for example, an array would have.)

Certain methods may have blocks passed to them. This is true of all iterators, whether built-in or user-defined. A block is usually passed as a do-end block or a brace-delimited block; it is not treated like the other parameters preceding it, if any. See especially the example:

my_array.each do |x|   some_action end { |f| some_action }

Named parameters will be supported in a later Ruby version but are not supported at the time of this writing. These are called keyword arguments in the Python realm; the concept dates back at least as far as the Ada language.

Methods may take a variable number of arguments:

receiver.method(arg1, *more_args)

In this case, the method called treats more_args as an array that it deals with as it would any other array. In fact, an asterisk in the list of formal parameters (on the last or only parameter) can likewise "collapse" a sequence of actual parameters into an array:

def mymethod(a, b, *c)   print a, b   c.each do |x| print x end end mymethod(1,2,3,4,5,6,7) # a=1, b=2, c=[3,4,5,6,7]

Ruby has the capability to define methods on a per-object basis (rather than per-class). Such methods are called singletons; they belong solely to that object and have no effect on its class or superclasses. As an example, this might be useful in programming a GUI; you can define a button action for a widget by defining a singleton method for the button object.

Here is an example of defining a singleton method on a string object:

str = "Hello, world!" str2 = "Goodbye!" def str.spell   self.split(/./).join("-") end str.spell      # "H-e-l-l-o-,- -w-o-r-l-d-!" str2.spell     # error!

Be aware that the method is defined on the object, not the variable.

It is theoretically possible to create a prototype-based object system using singleton methods. This is a less traditional form of OOP without classes. The basic structuring mechanism is to construct a new object using an existing object as a delegate; the new object is exactly like the old object except for things that are overridden. This enables you to build prototype/delegation-based systems rather than inheritance based, and, although we do not have experience in this area, we do feel that this demonstrates the power of Ruby.

The Ruby Way(c) Solutions and Techniques in Ruby Programming
The Ruby Way, Second Edition: Solutions and Techniques in Ruby Programming (2nd Edition)
ISBN: 0672328844
EAN: 2147483647
Year: 2004
Pages: 269
Authors: Hal Fulton

Similar book on Amazon © 2008-2017.
If you may any questions please contact us: