"Design Patterns" [GoF] prescribes two commandments to live by:
1) Program to an interface, not an implementation. 2) Favor object composition over class inheritance.
This implies that every class should have a defined interface, and that every class should be a composition of objects. However, there is often a large barrier to object composition - sometimes a class needs to have the interface of its components. You don't want to extend the functionality of the component, you just want its interface, and you want all calls to that interface to be sent to the component object. In this case, it is so much easier to inherit even though you are not really extending the functionality of the parent class (this especially happens when writing GUI applications, where the components have huge APIs). If you bite the bullet and make a composite, you end up writing a lot of wrapper functions (see Figure 1). (A wrapper function has the same function signature as the function it is wrapping, and it simply calls the same function in the target object.) No one likes to write wrapper functions - its dull, boring, menial work. I've thrown out plenty of my own designs because they would have required me to write wrappers for a huge number of functions (for example, the wxWindow class has over 130 member functions. See http://www.wxWindows.org), so I end up inheriting even though I know it is "wrong".
There is no way around this problem in C++ and Java (though maybe an IDE vendor could put in a tool to automatically generate wrapper functions). Amazingly enough, however, Python allows you to modify classes at run time. This means it is possible to make Python automatically write these wrapper (or proxy) functions for you.
Python, 131 lines
The code takes advantage of the meta-programming aspects of Python. A Python class can be modified at run-time. For example, member functions or member variables can be added or removed on the fly. Most of the time this isn't very useful and is usually extremely dangerous. However, if used in the right way, it allows us to develop new programming patterns that may not otherwise be possible.
Every object in python has a .__class__.__dict__ variable which contains a dictionary of the object's namespace, which includes member functions and member variables. We can add a member function or member variable to the class by adding it to this dictionary.
Before we start on the code, let's define some terms so that we are all on the same page. The container class will be called ProxyClass, and an instantiated object of that class will be called ProxyObject. The component object inside the ProxyClass that we want to wrap will be called the TargetObject and its class will be called TargetClass.
The API for this code is just one function named proxyInterfaceOf(ProxyObject, TargetObject). The result of calling this function is that all public functions of the TargetObject are wrapped by the ProxyObject. For example, if an object Foo started with functions named f1() and f2(), and an object Bar had functions named f3() and f4(), after calling proxyInterfaceOf(Foo, Bar), Foo would then have functions f1(), f2(), f3(), and f4(). The functions f3() and f4() in the Foo object would simply be wrappers which call the function of the same name in object Bar.
The proxyInterfaceOf() function works by iterating through each public member function in the TargetObject, creating ProxyFunction()s in the ProxyObject that will forward requests to the TargetObject. Note that a ProxyFunction() is a dynamically created function - you don't encounter this sort of thing every day. The code for these functions is written when the program executes. A function named createProxyFunction() generates the code for each ProxyFunction(), and every ProxyFunction() contains exactly the same code. If the code is the same every time, how does each ProxyFunction() know which function in the TargetObject to call? The answer is that we use a private data member in the ProxyObject to keep a map of:
[ ProxyFunction : ( TargetObject, TargetFunction ) ].
Let's call this the __functionMap. Now when the ProxyFunction() is called, it looks up itself in this map (it can do this because each ProxyFunction() has its own unique memory address), finds the cooresponding TargetObject and TargetFunction, and makes the call with all the arguments that it (the ProxyFunction()) received.
How is this __functionMap created? This is where things can be a little confusing (as if they werent already?) The __functionMap is created in proxyInterfaceOf(), and it is added as a data member to the ProxyObject (once again using dynamic programming). The weird thing is that ProxyFunction must exist before an entry can be placed in the __functionMap. Thus, the ProxyFunction() is generated by createProxyFunction(), and then the address of that ProxyFunction() is placed in the __functionMap, along with its targets. This is a little clearer when you think that the code for the ProxyFunction() is being written, not executed, in createProxyFunction(). The final step in this whole process is to add ProxyFunction() to the class dictionary of the ProxyObject.
To summarize, there are two separate run-time stages to this paradigm: Initialization and Invocation.
Initialization Input: ProxyObject, TargetObject 1) Create __functionMap as private data member in ProxyObject 2) Create a ProxyFunction() 3) Map the ProxyFunction() to (TargetObject,TargetFunction) in the __functionMap 4) Add the ProxyFunction() to ProxyObject's class dictionary, using the TargetFunction as the dictionary key
Invocation When a ProxyFunction() is called, it does the following: 1) Get the class dictionary for the ProxyObject 2) From the class dictionary, get the private data member __functionMap 3) From the __functionMap, look up ProxyFunction()'s own address to get the corresponding TargetObject and TargetFunction 4) Call TargetObject.TargetFunction with the arguments given to the ProxyFunction() 5) Return whatever TargetObject.TargetFunction returns
(See Figure 3 for a usage example) Make use of proxyInterfaceOf() with extreme caution. You should only use this function in the __init__ stage of an object. You have to make sure that none of the functions you write clash with the names of the TargetFunctions, otherwise the ones in your class will be replaced by the one in the TargetObject.
One of the problems with using the proxyInterfaceOf() function is that it leaves no traces of documentation. You must make sure to document that a class implements the interface of another object.
The code could be expanded to optionally proxy all the TargetObject's parent class functions as well, or proxy just a subset of the TargetObject's member functions instead of the entire set. There should also be warnings or better default behavior when there is a namespace clash - that is, when a function being added has the same name as a function that already exists in the class.
There also may be better ways to code proxyInterfaceOf(), perhaps using Python's "compile/exec" functions so that the mapping problem does not occur. As some of you may have noted, doing lookups for each function call will slow things down. If the ProxyFunction()s could be generated so that this lookup did not have to occur, this would speed things up.
We have seen one way in which meta-programming can be used for the force of good. We can uphold the Gang of Four's two main tenets without causing ourselves undo misery. Python has allowed us this freedom - it gives us the ability to explore options like these. This new functionality allows us to rid ourselves of inheritance forever. May our sins be absolved and may we always favor object composition over inheritance.
All suggestions, comments, and bugs are welcome.
[Gof] "Design Patterns", Gamma, et al., Addison-Wesley, 1995.