|
Summary
Classes are a well-known object oriented feature. They allow you to group common behaviours which apply across sets of objects. They also declare the member variables inherent in all objects belonging to these sets. All this is great until you want to change the behaviour that applies to these objects as a group. This is when you need something like class redefinition.
What is Class Redefinition?
Simply put, class redefinition is the ability to redefine a class. This has the effect of changing the behaviour that the instances of the class exhibit. This is useful in situations where the behaviour of the instances needs to change immediately.
Why use Class Redefinition?
It is possible to consider a class a set of functionality (represented by the methods and members) that is available to instances of the class. In this case, if you wish the functionality to be used by an object then that object must inherit from the class. But what happens when you want an object to exhibit the behaviour of other classes? In this case you must inherit from the other classes as well.
This approach can lead to a fairly complex degree of inheritance. This is of course particularly wasteful when all you need is the functionality provided by a class only at a particular point in the lifetime of an object. In this case, the object carries around the functionality of the class for a large majority of the time without using it. This also raises the cost of maintenance (some might call it the cost of confusion) when developers must figure out the complex inheritance structure that an object carries with it before debugging/making enhancements to the object. Wouldn't a service oriented approach be better? The answer is a resounding Yes. In a service oriented approach the object could get the services it needed when it needed them and then discard them, carrying no extra burden. This service oriented approach is possible via class redefinition.
With class redefinition it is possible to change the behaviour of the objects in the class at the point when you need the new behaviour. Then when you are done you can revert to the old class behaviour. This saves you from the complexity presented in the previous paragraphs. In addition, this allows a form of exceptional inheritance (if we may call it that) where the instances temporarily inherit from another class that offers needed services at the point when they are needed.
In short, by using class redefinition, objects can get the services they need at the points when they need them. They do not need to carry them around unnecessarily for the rest of the time. This is service oriented architecture with the classes acting as the services and the objects as the clients who use the services when they need them.
How do you implement Class Redefinition in Superx++?
Superx++ performs class redefinition through class interfaces. The class interfaces provide a buffer between the instances and the class implementations that allows the redefinition to occur. It works as follows:
Consider the following class called XPerson:
<class name=XPerson>
<scope type=public>
<func type=void name=DoStuff>
<body>
<xout>\r\n<eval object=this method=tag /> says Hello world!</xout>
</body>
</func>
</scope>
</class>
|
Now let us create the class interface (of the same name as the class) that will act as the buffer between the class and its instances.
<class name=XPerson header=true lib=XPerson.dll>
<scope type=public>
<func type=void name=DoStuff />
</scope>
</class>
|
The class interface is distinguished from the regular class by the use of the header=true attribute in the <class> statement. In addition, the class interface points to the class executable via the lib attribute. The XPerson.dll is pointed to which means that the class DLL that is created for the class XPerson is the one that gets invoked whenever methods in this class interface are invoked.
Okay, now that we have the class (the implementation) and the class interface, let us create some instances that inherit from the class interface.
<node name=Tom class=XPerson />
<node name=Dick class=XPerson />
<node name=Harry class=XPerson />
|
The three objects above are instances of the class interface called XPerson. Alright, now let us call the DoStuff method that is declared in the XPerson class interface that the objects inherit from.
<eval object=Tom method=DoStuff />
<eval object=Dick method=DoStuff />
<eval object=Harry method=DoStuff />
|
The output we expect to see based on the calls to DoStuff above is:
Tom says Hello world!
Dick says Hello world!
Harry says Hello world!
|
The code snippets in the text above this line all represent what you can do with normal inheritance. Now let us start down the path of class redefinition.
The first thing we must do is create a new definition that will contain the new behaviour of the objects.
<class name=XPerson1>
<scope type=public>
<func type=void name=DoStuff>
<body>
<xout>\r\n<eval object=this method=tag /> says Bye world!</xout>
</body>
</func>
</scope>
</class>
|
This is of course a new class. However, watch what we do with the class interface. We change the path to the class executable contained in the class interface's lib attribute. Instead of pointing to XPerson.dll, we will point the lib attribute to XPerson1.dll. This is done in the following line:
<eval object=classes/class/@name=XPerson attribute=lib>XPerson1.dll</eval>
|
The meaning of the line above, is to get the class node in the classes special object whose name attribute is XPerson (this is the class interface's node in the classes special memory object) and set its lib attribute to XPerson1.dll. The real power of this single line of code is to change the class interface from pointing to the old implementation (the old behaviour) to the new implementation (the new behaviour). Therefore all instances that have inherited from this class interface will now behave differently; i.e. according to the new behaviour.
So let us call the same DoStuff method on the same three objects. We do so in the exact same way we did before:
<eval object=Tom method=DoStuff />
<eval object=Dick method=DoStuff />
<eval object=Harry method=DoStuff />
|
The output we now expect to see based on the calls to DoStuff above is:
Tom says Bye world!
Dick says Bye world!
Harry says Bye world!
|
Notice that the output is different from the output produced by the same DoStuff method call before the lib attribute of the class interface was redirected. From the perspective of the objects, the objects have had their class (i.e. XPerson) redefined. This is what caused the change in their behaviour. Once the new behaviour has served its purpose you can change the lib attribute of the class interface back to what it was. This is how the services of the classes are used when needed and discarded when they are no longer required.
New Behaviour
In the preceding code snippets we have seen an example built where the DoStuff method that was invoked changed (from the perspective of the objects). Yet the DoStuff method existed in both versions (old and new). It is possible for the new behaviour set to contain new methods that are available to the method. This is handy when the new methods represent extra functionality that you want to add to the objects to perform special tasks. Then once the tasks are over you can revert the objects back to the old behaviour. All this is accomplished using the very same class redefinition technique we have covered above using the lib attribute of the class interface that the objects inherit from.
Syntax Note
The code snippets above are just that: code snippets. They do not constitute a complete program. The complete program is listed here:
<?xml version=1.0 encoding=utf-8 ?>
<xpp xmlns=http://www.superxlang.org/NS>
<!--
This program demonstrates class redefinition through a class interface.
1) Define multiple versions of a class with different names.
2) Create a class interface to point to any of the classes.
3) Create an object that inherits from the class interface.
4) At some arbitrary point, change the lib attribute of the class interface
to point from one class to another.
It is step 4 above that demonstrates the class redefinition (relative to the
object that inherits from the interface). This is how to change the behaviour
of the objects that inherit from a particular class interface.
Factors to consider:
1) All objects inheriting from class interfaces will have their behaviour
changed when the interface changes.
If you want to change the behaviour of only one object, use
object reclassification. For object reclassification, just change the class
attribute of the object in mem to the new class(es).
-->
<!-- define two classes which have the same set of methods -->
<class name=XPerson>
<scope type=public>
<func type=void name=DoStuff>
<body>
<xout>\r\n<eval object=this method=tag /> says Hello world!</xout>
</body>
</func>
</scope>
</class>
<class name=XPerson1>
<scope type=public>
<func type=void name=DoStuff>
<body>
<xout>\r\n<eval object=this method=tag /> says Bye world!</xout>
</body>
</func>
</scope>
</class>
<!-- define the class interface and point it to the XPerson class -->
<class name=XPerson header=true lib=XPerson.dll>
<scope type=public>
<func type=void name=DoStuff />
</scope>
</class>
<!-- create the Tom, Dick and Harry objects which inherits from the XPerson
class interface.
The binding is to the class interface rather than the class because
we have the class interface defined in this program together with
the class. -->
<node name=Tom class=XPerson />
<node name=Dick class=XPerson />
<node name=Harry class=XPerson />
<!-- the following call to DoStuff is to the XPerson::DoStuff method
because the XPerson class interface is pointing to the XPerson.dll -->
<eval object=Tom method=DoStuff />
<eval object=Dick method=DoStuff />
<eval object=Harry method=DoStuff />
<!-- Class Redefinition through the Class Interface occurs in this line:
Set the lib attribute of the XPerson class interface in the classes
special object to point to XPerson1.dll.
From here on out, the XPerson class interface points to the implementation
file called XPerson.dll -->
<eval object=classes/class/@name=XPerson attribute=lib>XPerson1.dll</eval>
<!-- the following call to DoStuff is to the XPerson1::DoStuff method
because the XPerson class interface is pointing to the XPerson.dll -->
<eval object=Tom method=DoStuff />
<eval object=Dick method=DoStuff />
<eval object=Harry method=DoStuff />
</xpp>
|
The output of the program above is:
Tom says Hello world!
Dick says Hello world!
Harry says Hello world!
Tom says Bye world!
Dick says Bye world!
Harry says Bye world!
|
More Information?
The preceding has been a brief introduction to the topic of class redefinition. I hope that it has been of some use to you and has whetted your appetite, so to speak. For more information on this and other topics about Superx++ you can go to the Emergent Logic web site at http://www.emergentlogic.com. If you have any questions or comments for the author of this article please e-mail me at matikm@emergentlogic.com.
Thanks,
Kimanzi Mati
Emergent Logic LLC
|