Getting Started With Ruby: Object-Oriented Approach |
Written by Mike James | ||||
Monday, 25 March 2013 | ||||
Page 3 of 3
InheritanceNow 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:
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:
With this definition we could now create a new object and call the inherited sum method:
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:
or you could write
which would call the inherited but overridden sum method to add @x and @y, Access ControlA Ruby object only has methods that are visible to the outside world and there are three levels of "visibility" that you can define.
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:
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:
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. TypeYou 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 Informationhttp://www.jetbrains.com/ruby/ Related ArticlesGetting 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, Facebook, Google+ or Linkedin, or sign up for our weekly newsletter.
Comments
or email your comment to: comments@i-programmer.info
|
||||
Last Updated ( Monday, 29 April 2013 ) |