From: Rony G. F. <Ron...@wu...> - 2007-08-23 19:08:06
|
Hi there, this post just has the purpose to look at mulitple inheritance as currently implemented in ooRexx, drawing conclusions about the rules that govern it. *Defining Classes, (Single) Inheritance * First a few definitions given the following code ("test0.rex"): -------------------- cut here ("test0.rex") ---------------------- t1=.test~new("Clara") say "t1:" t1~string -- usees value of attribute "ObjectName" say "---" t2=.test~new say "t2:" t2~string -- usees value of attribute "ObjectName" /* ----------------------------------------------------------- */ /* class "TEST" inherits class "NiceInfo" and "AdditionalInfo" */ ::class Test subclass object ::method init use arg name=("#" self~identityHash) self~objectname=name say "arrived in test:init:" self~objectName self~init:super -- invoke superclass' init method -------------------- cut here ("test0.rex") ---------------------- The class "Object" (the root class in ooRexx) is *specialized* by the class "Test". "Test" is a *subclass* of "Object". "Object" is a *superclass* (a.k.a. *parent class*) of "Test". Sending a message to a Test object may resolve to a method in one of Test's superclasses (in this example there is just one: "Object"), if the method is not part of the Test object methods. If the method is not found in its superclass and if the superclass has a superclass (a *grandparent* class) as well, then resolving proceeds in the grandparent class. The terminator class for this resolving is the root class "Object". If the method is not found in the root class as well, a runtime error is raised (cf error number 97.1, "Object ... does not understand message .....") Because of this mechanism, one can also state that a Test object *inherits* all the methods of all of its superclasses. In the case that a programmer wants the resolving for a method to not start in the class which created the object, then one can append a colon (:) followed by a class object, in which the search for the method should start. It is thereby possible to leave out superclasses and start with the search way up in the hierarchy. The class object of the next superclass to search is always stored in the Rexx set variable *super* ("self" and "super" are variables that get always set by Rexx for methods). One conclusion is that all the attributes and methods defined in the root class "Object" will always be available to all the direct and indirect subclasses. It will always be possible in a method to directly address a method of the root class by adding ":.Object" to a message, which will cause the object to start looking for a matching method in the given class (i.e. the root class "Object"). In the class "Test" in the above example program "test0.rex" the following can be observed: * there exists a constructor method "init" which gets invoked upon creation of a new instance * "init" retrieves an argument "name" and sets it to a default value, in case the argument is omitted (i.e., is not supplied with the "new" method) o the default value uses a method named "identityHassh", which is new to ooRexx 3.2.0 and is stored with the root class "Object"; via the inheritance resolution this method will be eventually found and executed, its result retrieved (a unique numeric string) o the value of the local variable "name" is assigned to the attribute named "objectName" which is defined in the root class "Object". This value can be retrieved later by sending the attribute's name as the message ("~objectName"). Whenever a string is created from an object the value of this attribute "objectName" is used as the resulting string. o the last statement in the method "init" of the class "Test" is "self~init:super": this causes the constructor of "Test"'s superclass to be run (i.e. the method 'init' in the root class "Object"). Without ":super" method resolution would start in the class "Test" and would invoke the constructor recursively. Running the above program "test0.rex" on ooRexx 3.2.0 yields the following output: -------------------- cut here (output of running "test0.rex") ---------------------- arrived in test:init: Clara t1: Clara --- arrived in test:init: # 535948878 t2: # 535948878 -------------------- cut here (output of running "test0.rex") ---------------------- The first Test object "t1" gets a name explicitly supplied ("Clara"), the second Test object omits the argument, such that a default value ("# 535948878") gets created --- *Defining Classes, Multiple Inheritance Inheriting One Mixin-Class * First a few more definitions given the following code ("test1.rex"): -------------------- cut here ("test1.rex") ---------------------- t1=.test~new("Clara") say "t1:" t1~string -- usees value of attribute "ObjectName" say "t1:" t1~niceName -- uses class "NiceInfo" say "---" t2=.test~new say "t2:" t2~string -- usees value of attribute "ObjectName" say "t2:" t2~niceName -- uses class "NiceInfo" /* ----------------------------------------------------------- */ /* class "TEST" inherits class "NiceInfo" and "AdditionalInfo" */ ::class Test subclass object inherit niceInfo ::method init use arg name=("#" self~identityHash) self~objectname=name say "arrived in test:init:" self~objectName self~init:super -- invoke superclass' init method /* ----------------------------------------------------------- */ /* class "NiceInfo", adds "niceName" method */ ::class NiceInfo mixinclass object ::method init say "arrived in NiceInfo:init:" self~objectName forward class (super) -- invoke superclass' init method ::method niceName -- just adds info return "!nice!" self~objectname "!name!" -------------------- cut here ("test1.rex") ---------------------- There is a new class introduced, named "NiceInfo": * This class is a *mixin* class, because it uses "mixinclass" instead of "subclass" to specialize the root class "Object"! All classes that have "Object" as one of their superclasses are allowed to *inherit* all of the methods of a mixin class. If there were additional classes that would use "mixinclass Object", then all of these classes qualifiy for inheriting as well! Its constructor outputs debug information and then invokes the constructor of its superclass, this time using the keyword instruction "forward class (super)" instead of "self~init:super", which would have also worked in this case. (The "forward" keyword instruction has a very nice feature: it will by default forward all received arguments automatically.) This class adds a new method named "niceName" which embeds the object's name in the strings "!nice!" and "!name!" and returns that string. * The class "Test" now adds the subdirective "inherit" followed by the mixin class named "NiceInfo". This has the effect that the method resolution gets "detoured": if a method cannot be found in the "Test" object, then first the inherited class is looked-up next, followed by the specialized class "Object" last! Hence the method resolution goes like this: "Test" -> "NiceInfo" -> "Object". This is the very reason why it becomes possible to send a Test object the message "niceName". As one can see the benefit here is the fact, that a new class ("Test") is able to gain the methods of another, already implemented mixin class. The methods of the mixin class are resolved *before* the specialized class "Object" (class "Test" specializes "Object", hence "Object" is the "specialized" class). [There is another interesting aspect here: it is possible at runtime to dynamically add or remove mixin classes from a class, which opens up a lot of power in addition!] Running the above program "test1.rex" on ooRexx 3.2.0 yields the following output: -------------------- cut here (output of running "test1.rex") ---------------------- arrived in test:init: Clara arrived in NiceInfo:init: Clara t1: Clara t1: !nice! Clara !name! --- arrived in test:init: # 535954786 arrived in NiceInfo:init: # 535954786 t2: # 535954786 t2: !nice! # 535954786 !name! -------------------- cut here (output of running "test1.rex") ---------------------- *Defining Classes, Multiple Inheritance Inheriting MORE than One Mixin-Class, I * First the definitions given in the following code ("test2.rex"): -------------------- cut here ("test2.rex") ---------------------- t1=.test~new("Clara") say "t1:" t1~string -- usees value of attribute "ObjectName" t1~addInfo("new infos4clara") -- uses class "AddInfo" say "t1:" t1~niceName -- uses class "NiceInfo" say "---" t2=.test~new say "t2:" t2~string -- usees value of attribute "ObjectName" t2~addInfo -- uses class "AddInfo" say "t2:" t2~niceName -- uses class "NiceInfo" /* ----------------------------------------------------------- */ /* class "TEST" inherits class "NiceInfo" and "AddInfo" */ ::class Test subclass Object inherit NiceInfo AddInfo ::method init use arg name=("#" self~identityHash) self~objectname=name say "arrived in test:init:" self~objectName self~init:super -- invoke superclass' init method /* ----------------------------------------------------------- */ /* class "NiceInfo", adds "niceName" method */ ::class NiceInfo mixinclass Object ::method init say "arrived in NiceInfo:init:" self~objectName forward class (super) -- invoke superclass' init method ::method niceName -- just adds info return "!nice!" self~objectname "!name!" /* ----------------------------------------------------------- */ /* class "AddInfo", adds "additionalInfo" method */ ::class AddInfo mixinclass Object ::method init say "arrived in AddInfo:init:" self~objectName forward class (super) -- invoke superclass' init method ::method addInfo use arg addInfo="(additional infos)" self~objectName=self~ObjectName addInfo return self~objectName -------------------- cut here ("test2.rex") ---------------------- There is another new mixin class introduced, named "AddInfo": * This class is a *mixin* class, because it uses "mixinclass" instead of "subclass" to specialize the root class "Object"! It possesses a constructor (a method named "init"), and a method named "additionalInfo". * The class "Test" now uses "inherit NiceInfo AddInfo". This has the effect that the method resolution gets "detoured": if a method cannot be found in the "Test" object, then first the inherited mixin class "NiceInfo" is looked-up then the inherited mixin class "AddInfo", lastly followed by looking up the specialized class "Object"! Hence the method resolution goes like this: "Test" -> "NiceInfo" -> "AddInfo" -> "Object". As one can see the benefit here is the fact, that a new class ("Test") is able to gain the methods of other, already implemented mixin classes. The methods of the mixin class are resolved *before* the specialized class "Object" (class "Test" specializes "Object", hence "Object" is the "specialized" class). [There is another interesting aspect here: it is possible at runtime to dynamically add or remove mixin classes from a class, which opens up a lot of power in addition!] Running the above program "test2.rex" on ooRexx 3.2.0 yields the following output: -------------------- cut here (output of running "test2.rex") ---------------------- arrived in test:init: Clara arrived in NiceInfo:init: Clara arrived in AddInfo:init: Clara t1: Clara t1: !nice! Clara new infos4clara !name! --- arrived in test:init: # 535961214 arrived in NiceInfo:init: # 535961214 arrived in AddInfo:init: # 535961214 t2: # 535961214 t2: !nice! # 535961214 (additional infos) !name! -------------------- cut here (output of running "test2.rex") ---------------------- *Defining Classes, Multiple Inheritance Inheriting MORE than One Mixin-Class, II * First the definitions given in the following code ("test3.rex"): -------------------- cut here ("test3.rex") ---------------------- t1=.test~new("Clara") say "t1:" t1~string -- usees value of attribute "ObjectName" t1~addInfo("new infos4clara") -- uses class "AddInfo" say "t1:" t1~niceName -- uses class "NiceInfo" say "---" t2=.test~new say "t2:" t2~string -- usees value of attribute "ObjectName" t2~addInfo -- uses class "AddInfo" say "t2:" t2~niceName -- uses class "NiceInfo" /* ----------------------------------------------------------- */ /* class "TEST" inherits class "NiceInfo" and "AddInfo" */ ::class Test subclass Object inherit NiceInfo AddInfo ::method init use arg name=("#" self~identityHash) self~objectname=name say "arrived in test:init:" self~objectName self~init:super -- invoke superclass' init method /* ----------------------------------------------------------- */ /* class "MixinClassesBase" */ ::class MixinClassesBase mixinclass Object ::method init say "arrived in MixinClassesBase:init:" self~objectName forward class (super) -- invoke superclass' init method /* ----------------------------------------------------------- */ /* class "NiceInfo", adds "niceName" method */ ::class NiceInfo mixinclass MixinClassesBase ::method init say "arrived in NiceInfo:init:" self~objectName forward class (super) -- invoke superclass' init method ::method niceName -- just adds info return "!nice!" self~objectname "!name!" /* ----------------------------------------------------------- */ /* class "AddInfo", adds "additionalInfo" method */ ::class AddInfo mixinclass MixinClassesBase ::method init say "arrived in AddInfo:init:" self~objectName forward class (super) -- invoke superclass' init method ::method addInfo use arg addInfo="(additional infos)" self~objectName=self~ObjectName addInfo return self~objectName -------------------- cut here ("test3.rex") ---------------------- "test3.rex" now has the following definitions: * The class "MixinClassesBase" which is a mixin class of "Object" * The mixin classes "NiceInfo" and "AddInfo" which are mixin classes of "MixinClassesBase" and by the rules of transitivity they are also mxin classes of "Object" (as "MixinClassesBase" is a mixin of "Object")! * The class "Test" which inherits the mixin classes "NiceInfo" and "AddInfo", and also "MixinClassesBase" by the normal method resolution mechanism of ooRexx as it is the superclass for "NiceInfo" and "AddInfo". This now yields the following method lookup sequence: "Test" -> "NiceInfo" -> "AddInfo" -> "MixinClassesBase" -> "Object" As one can see the benefit here is the fact, that a new class ("Test") is able to gain the methods of other, already implemented mixin classes. The methods of the mixin class are resolved *before* the specialized class "Object" (class "Test" specializes "Object", hence "Object" is the "specialized" class). [There is another interesting aspect here: it is possible at runtime to dynamically add or remove mixin classes from a class, which opens up a lot of power in addition!] Running the above program "test3.rex" on ooRexx 3.2.0 yields the following output: -------------------- cut here (output of running "test3.rex") ---------------------- arrived in test:init: Clara arrived in NiceInfo:init: Clara arrived in AddInfo:init: Clara arrived in MixinClassesBase:init: Clara t1: Clara t1: !nice! Clara new infos4clara !name! --- arrived in test:init: # 535967144 arrived in NiceInfo:init: # 535967144 arrived in AddInfo:init: # 535967144 arrived in MixinClassesBase:init: # 535967144 t2: # 535967144 t2: !nice! # 535967144 (additional infos) !name! -------------------- cut here (output of running "test3.rex") ---------------------- *Concluding the Rules for Multiple Inheritance in ooRexx* * A "mixin class" uses the subdirective "mixinclass" instead of "subclass" to specialize a class. * Classes can inherit mixin classes, if they possess a superclass that the mixin classes specialize via "mixinclass". * Classes can inherit as many mixin classes as the coder wishes, the order stated in the subdirective "inherit" determines the order of looking up those mixin classes, when resolving a method. * Inherited mixin classes are honored *before* the specialized class (they get "injected" between the class that inherits and the class that it specializes). * The specialized class is looked up only, if neither inherited mixin class possessed the method that gets sought, such that the standard resolution takes on. Please forgive my somewhat longish post, but I thought that it may proof helpful, if thinking/discussing multiple inheritance in ooRexx. Also, it should allow for others to correct/pinpoint omissions/errors in these observations and/or conclusions. ---rony P.S.: Just a little afterthought, remark: Java does not possess multiple inheritance. The authors have been claiming that most of the benefits of multiple inheritance can be realized using Java Interfaces. In Java a class can implement as many Interfaces as it wishes ("multiple interfaces"). However, a Java programmer *must* then implement each and every method that is defined in each Interface. If such implemented interface methods are later needed for other classes, they must be re-implemented there as well (or with other words: Java in this case does not allow for re-using such implementations for different classes, if they are not subclasses of the class that implemented the interfaces) . By contrast ooRexx allows mutliple inheritance not only to state what mixin classes get inherited (which can be also used to classify/"mark" a class multiple times), but ooRexx also allows to re-use the existing implementation ! |