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
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,
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.
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.
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).
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!