Getting Started With Ruby: Object-Oriented Approach
Written by Mike James   
Monday, 25 March 2013
Article Index
Getting Started With Ruby: Object-Oriented Approach
Accessors
Inheritance and Type

Inheritance

Now we come to the really interesting question: of how does Ruby implement inheritance given it is fully object based but doesn't have true classes? 

The answer is that Ruby basically implements a copy-based inheritance.

When you define a new class which inherits from Point say:

class Point3D < Point

end

then it is as if you had written the entire definition of Point within the definition of Point3D.

What this means is that Point3D inherits all of the instance methods and hence instance variables contained within it. Class variables i.e. @@ are also copy inherited but these remain associated with the super class, i.e. they are shared between the super class and the new child class - which it not what you might expect. Class instance variables, i.e. @, behave in a more reasonable way in that copy inheritance has no effect on them at all, i.e. they still belong to the original class object, and they are effectively not inherited.

Notice that if you think of Ruby inheritance as a "copy" or cloning operation between the base class object and the child class object then lots of things become clear. For example any methods which are dynamically added to the base object or any instances of the base object are not inherited by the child class. Only the basic definition of the class as contained in the class object is inheritable. 

You can override any method including private methods simply by redefining them and as Ruby is a dynamic language late binding is the only option and which method is actually used depends on its latest definition. That is there is no confusion or complication cased by the distinction between virtual and non-virtual methods or classes. An object is what it is defined to be and an instance of an object is initially what its class object is defined to be.

There is also a super statement that you can use to call a method belonging to the super class. If you call super in a method then the method of the same name in the parent class is executed. 

Lets take a look at a simple example of creating Point3D from Point2D. First we would need the initialize to call the Point2D intialize to store two of the co-ordinates:

class Point3D < Point
 def initialize(x,y,z)
  @z=z
  super(x,y)
 end
end

With this definition we could now create a new object and call the inherited sum method:

p=Point3D.new(1,2,3)
puts p.sum

Of course the inherited sum method only adds the first two co-ordinates together. To create a full sum of all three co-ordinates we need to override the inherited method:

def sum
 @x+@y+@z
end

or you could write

def sum
 super+@z
end

which would call the inherited but overridden sum method to add @x and @y, 

Access Control

A Ruby object only has methods that are visible to the outside world and there are three levels of "visibility" that you can define.

 

  • Public methods can be called by anyone - this is the default.
  • Protected methods can be invoked only by objects of the defining class and its subclasses.
  • Private methods private methods can be called only in the defining class and by descendants.

 

At first look these seem much like the access control mechanisms of other object oriented languages but they are a little more specialized than you might think. 

In Ruby a method can be called with a receiver object i.e. its current context if you like. A private method cannot be called with an explicit receiver specified. So if a method is private you cannot write object.method() but you can write method(). What this means is that a private method cannot be called on an instance of an object. However because the default context is correct within the class that a private method is defined in you can use it within the class and in direct descendants.

For example:

class Point
 def initialize(x,y)

  @x,@y=x,y

 end

 def sum

  total

 end

 private

  def total

   @x+@y

  end
end

In this case we have defined total to be a private method. This means that sum can call total to do its work. However notice that this would fail it it was called using self.total even though self is set to Point and it specifies the same method. The rule is that you cannot write an explicit receiver. The Point3D class and any class that inherits from it can also make use of total. 

Protected works in the same way but now you can use self as an explicit receiver or a class that is the same as the current value of self. What this means is that a protected method can be called from outside of the class object but again only when the context is correct for the call.  This is doesn't occur very often, but the prime example is when you need to access another object's method. For example suppose we add to Point the method:

def compare(c)
 c.total>total
end

this uses the method total to add @x and @y for both the current object and another instance passed as a parameter. Now we want the use of total to be restricted only to Point objects and this can be achieved by declaring it as private. However if total is declared as private then c.total will not work because it is called with an explicit receiver. The correct thing to do is to declare total as protected so that it can be called with a receiver that is the same as the current self. In this case self if Point and as long as c is an instance of Point or a derived class then the call to total will work. Notice that this protects you from c not being a child class of Point and from the instance using the compare code not being an instance of Point. 

Ruby's protection mechanism is subtle but it seems to work.  

Type

You may be wondering what type has to do with any of this?

The simple answer is that type hardly ever crops up in a Ruby program. The most that type ever enters the argument is in the discussion of protected when two objects have to be part of the same object hierarchy. 

Everything is an object and objects simply differ according to what methods they have.

On this level there is very little concept of type at all. Basically all that matters in constructing a program is whether  the object has the method you are trying to use (recall properties are implemented as methods).

It is up to you to test for the existence of methods with the correct call signature explicitly and there are Ruby statements that let you do this. This is very similar to JavaScript but Ruby has one big advantage. if a method is not present on an object eventually a catchall default method, method_missing, will be called and by customizing this you can create objects that dynamically add methods as required. For example suppose you need to create an object that encapsulates a data record. Then you can allow the user to call methods with any of the names of the fields but only implement the catchall method_missing.  The method_missing code could simply discover the name of the method that the user tried to use, it's passed as the first parameter, and then return the data corresponding to this field. The user would be convinced that your object has a method for each field but in fact you have only had to implement one. This is an example of meta programming which is something Ruby excels at. 

Also notice that the lack of type means that functions are not distinguished by their signatures, i.e. parameter types. This immediately makes the whole idea of overloading, i.e. providing multiple definitions of the same function according to signature, redundant. There is no overloading but you don't need it. Any function can be written to deal with any signature by simply testing that each parameter is appropriate for the job. Similarly there is no need to invent the concept of generics as every method is generic simply because there is no strong typing to worry about!

Overall, Ruby has a dynamic approach to objects that is backed up by some very practical ways of building and using objects. If you are more familiar with the strict, static typing approaches of languages such as Java or C# then you will find the whole approach scary. If, however, you come from a JavaScript background then you will be wondering what the fuss is about. 

There is more than one way to skin a duck.  

More Information

http://www.ruby-lang.org/en/

http://www.ruby-doc.org/

RubyInstaller

RVMinstallation page

http://www.jetbrains.com/ruby/

Related Articles

Getting Started With Ruby - A Functional Language

 

To be informed about new articles on I Programmer, install the I Programmer Toolbar, subscribe to the RSS feed, follow us on, Twitter, FacebookGoogle+ or Linkedin,  or sign up for our weekly newsletter.

 

espbook

 

Comments




or email your comment to: comments@i-programmer.info

 



Last Updated ( Monday, 29 April 2013 )