Monkey subclassing for fun and profit
Monkey subclassing is my own term for something that I wind up doing every so often, formed by analogy to monkey patching. Where monkey patching is changing the class itself, monkey subclassing is changing the class by subclassing it and then overriding internal method functions (generally undocumented ones, or at least ones not officially exposed as public interfaces). Monkey subclassing is different from regular subclassing in that I am playing fast and loose with the internals of someone else's code and in fact these classes may never have been designed to be subclassed to start with.
Monkey subclassing has the advantage that it only changes the class for you, not for all code in your program that's using the class. It has the potential disadvantage that you may need to force other bits of the program to use your new subclass instead of the original class, forcing you to monkey subclass them in turn and soon it is monkeys all the way up.
(This depends on what sort of a change you want; sometimes you're happy with a private version of the class with a particular feature that you need.)
All of this is abstract, so let me make it concrete. Django has a few
options for sending
email, but for our purposes none of them were ideal; we could either
send via SMTP with its problems
or invoke sendmail
directly, which didn't offer enough control of
headers and suchlike. What we wanted to do was talk SMTP to the local
mailer over a pipe, which would give us the full control we needed while
meaning that we didn't have to worry about handling retries and queueing
ourselves, but there is no canned solution for that right now in Django
(or at least none that I saw when I went looking).
Talking SMTP over a pipe to a local process is very similar to
talking SMTP over the network to a remote SMTP server, and Django
mostly uses standard Python things to do the latter. So I made two
monkey subclasses. First, I subclassed the standard Python smtplib's SMTP
class to have
a connect()
method that invoked the local mailer with the right
arguments in a subprocess instead of making a network connection.
Second, I monkey subclassed Django's standard SMTP email backend to have
an open()
method that used my new monkey 'local SMTP' subclass instead
of smtplib.SMTP to create its 'SMTP connection' that it would send mail
over. The net result was that with two relatively short routines I had
just what I wanted: our Django application sends mail using the local
mailer but with full control over the message envelopes and headers.
I don't think that either class considers the methods that I overrode in my subclasses to be public interfaces. In fact, I'm not sure that either class is intended to be subclassed by outside people. And certainly a code change in either module could easily break my subclassing; monkey subclassing is the fast way, not necessarily the morally correct way.
(The morally correct way to solve my problem would be to write my own Django email backend from scratch in order to do this, and never mind the code duplication and time involved.)
|
|