I'm writing to beg forgiveness. I was just doing my job, but before I knew it, I cavalierly sinned against all I have known of Objected Oriented programming since I began C++ in 1987. I confess: I have been changing the inheritance hierarchy at runtime.
As in most falls from grace, it began very innocently. My mission was to download a set files from a collection of web servers and test the files for integrity. The program, being IO bound was to be multi-threaded. Having adopted Python in recent years as my language of choice, I began blithely prototyping.
I created a base class that included the interface that I need to accomplish my task. One member function fetched the list of web servers and another fetched the list of files from those servers. Since I did not have immediate access to the database from which that information would come in the production environment, I subclassed my base class and overrode the functions that fetched that data for me. Now with three classes: the base, a database sourced subclass and my special static data test subclass, I thought I was pretty much ready.
Then came the sirens of feature creep. I heard them singing that they needed different types of integrity tests. It was obvious that more overriding of base class methods was the preferred solution. But wait, which subclass would I derive from? Production code would need these features, but I needed to test with my static data test subclass. My god, the object model was only a few hours old and already it was inadequate. I pondered multiple inheritance, but that got me nowhere. I considered splitting my object model into a collection of tightly coupled objects that could interact: a data fetching object for my two data sources, a test class that could be subclassed for each of the integrity test that I was to perform, and perhaps a manager class to orchestrate the whole thing. Yet a voice inside me was whispering, "do not proliferate objects unnecessarily. It is better to have one cohesive class hierarchy than a growing constellation of tightly coupled classes." I now know that voice was the devil, but now it is too late.
Unwilling to abandon my simple object model, overnight I thought about how to add features to different branches of an inheritance hierarchy simultaneously. Late that night I remembered a quote from a book on Python that I read years ago, "...you can even change the base class at runtime..." Perhaps, if I were able to create the inheritance hierarchy when I started the program and then use this modified hierarchy to create my objects, I'd have a solution. A few tests confirmed the theory.
Here's a simple class with two methods, 'f' and 'g'. All they do is print a c++ish identification of themselves.
class A:
def f(self):
print "A::f()" #mmm, C++ syntax
def g(self):
print "A::g()"
Then I wrote a couple more classes:
class B:
def g(self):
print "B::g()"
class C:
def f(self):
print "C::f()"
You'll note that I have no base classes. The first has a 'g' function while the second has an 'f' function. If they had class 'A' as a base class, they would be overriding the base class' functions.
Then I did this:
C.__bases__ = (A, )
B.__bases__ = (A, )
I made the two classes derivatives of the class 'A'. Now their respective 'f' and 'g' functions really did override the base class functions:
this prints, respectively, exactly what one would expect if 'C' derives from 'A':
Then I did this:
I've taken the class 'B' and inserted into the inheritance hierarchy between 'A' and 'C'. Rerunning the two calls to 'f' and 'g' using the object that is already instantiated, I get this output:
Not only did I change the inheritance hierarchy at runtime, but my changes took effect on objects that were already existing under the old inheritance scheme.
Heading back to my original programming task, I now had a method to add features to an existing class. I wrote classes that added one or two functions to the base class interface. If the user wanted a particular feature, it could be specified on the command line and then "mixed in" to the existing class hierarchy. The program collects the features it needs into one hierarchy, instantiates an amalgamated object and runs with it.
While I am confessing to a sin in this missive, I am also admitting that I will do it again. Like other so called sins that I have encountered in my life, this one was a lot of fun.