Translate

Saturday, March 2, 2013

Class Inheritance vs Class Wrapping

Qore supports "standard" class inheritance for polymorphism, but also supports another construct that may be unique to Qore.  In Qore there is the possibility of "wrapping" a class and providing a compatible interface with the wrapped class without using inheritance.

There are several advantages and also some drawbacks to this approach.  Let me first explain what I mean by "wrapping" a class and also explain how it's done in Qore.

To "wrap" a class, you embed an object of the desired class as a member of the new class and implement a methodGate() method that redirects method calls from outside the class to unimplemented methods.  Additionally a memberGate() method can be implemented to redirect accesses to unknown members from outside the class and return an arbitrary value for the member access.

For example, you can wrap a class to provide logging for all method calls or provide external thread synchronization to an unsynchronized class.  The following is an example that does both.

class MySocket {
    private {
        Socket $.sock();
        Mutex $.m();
    }

    static log(string $fmt) {
        vprintf(now_us().format("YYYY-MM-DD HH:mm:SS.us Z") + ": " + $fmt, $argv);
    }
    
    any methodGate(string $method_name) {
        MySocket::log("MySocket::methodGate() method called: %y args: %y\n", $method_name, $argv);
        $.m.lock();
        on_exit $.m.unlock();

        return callObjectMethodArgs($.sock, $method_name, $argv);
    }
}

This can be very useful for testing and for other purposes, requires very little typing to implement, and its purpose is clear from the implementation (even without comments).  Additionally, this method is completely independent of the definition of the wrapped class.

The main disadvantage is that it doesn't claim compatibility with the Socket class, so you cannot pass an object of class MySocket to code that expects a Socket object.

Currently there is no way in Qore to wrap a class in this way and to remain type-compatible (i.e. to appear to be a subclass of the wrapped class).  To remain type-compatible in this way, every method of the class has to be explicitly wrapped as above, and if the wrapped class has any method interface changes (for example, new method), the subclass has to changed accordingly.

However despite the drawbacks, it's still a very useful construct.   Possibly a future version of Qore will allow such wrapped classes to claim class compatibility with the wrapped class and therefore address the typing issues.