When it doubt, use more layers. We are going to make an otherwise useless object, but one designed to be very self documenting. It will only be a few lines long and contains stubs for the "public" functions every backend requires. The real object subclasses from public mock up. In this example, Irc()
is still psuedocode but Relay()
is taken straight from my actual source.
Generally, I do not "get" OOP. Objects have one good use in my book: they make convenient namespaces. 90% of the time I see people using a million objects where they are not really needed, solely because OOP lets you waste time writing a skeleton when you otherwise have no clue what to do.
But today I found a second redeeming quality to objects. Used sparingly, subclasses make great self documentation.
I've been working on a project with some peculiar communications requirements. For performance testing, there might be a dozen network backends to try. The first backend will use IRC. (It is an old and crufty spec, but one of the only with multicast.) Here's some pseudocode:
class Irc(object):
def __init__(self):
self.txq = Queue.Queue()
self.rxq = Queue.Queue()
self.irc = irclib.IRC()
self.server = self.irc.server()
self.irc.add_global_handler('pubmsg', self.handlePubMessage)
self.connect()
def connect(self):
self.server.connect(**server_info)
self.server.join(**channel_info)
def tick(self):
# batch send all txq messages
self.irc.process_once()
def handlePubMessage(self, connection, event):
self.rxq.put_nowait(event.something())
def get(self):
return self.rxq.get_nowait()
def send(self, message):
self.txq.put_nowait(message)
... and of course there are a lot of other event handlers to write (each parses an event into a message). And error handlers. These clutter up the object's namespace. The important parts are connect()
, tick()
, get()
and send()
. These methods will be common to every backend, but easy to get lost in the shuffle.
How do you make this core interface clear? There will probably be 50 functions, handlers, and parsers in the Irc
class. You'll have to look at the class for an hour to detangle them all. Adding clear docstrings would get half the way there, but that is not a good answer. Making all the IRC specific functions private would be the standard solution, but Python does not really have any concept of privacy.
class Relay(object):
"Subclass this and flesh it out."
def __init__(self):
self.client_setup()
self.client_connect()
def client_setup(self, **kwargs):
"Stuff that gets run once."
pass
def client_connect(self, **kwargs):
"Will be called multiple times, if disconnected."
pass
def tick(self):
"Called every frame from main loop."
pass
def get(self):
"Retrieve one message, or None."
pass
def send(self, message):
"Send a single message."
pass
class Irc(Relay):
def client_setup(self):
self.txq = Queue.Queue()
self.rxq = Queue.Queue()
self.irc = irclib.IRC()
self.server = self.irc.server()
self.irc.add_global_handler('pubmsg', self.handlePubMessage)
def client_connect(self):
self.server.connect(**server_info)
self.server.join(**channel_info)
def tick(self):
# batch send all txq messages
self.irc.process_once()
def handlePubMessage(self, connection, event):
self.rxq.put_nowait(event.something())
def get(self):
return self.rxq.get_nowait()
def send(self, message):
self.txq.put_nowait(message)
And that is one way to document through subclassing.
This appears to be the same functionality that Java interfaces pro...
subclasses from the public mock up