Descriptors in Python is an interesting approach to simplifying many difficult design problems. I would like to share one of my application of this useful feature. Consider the following code snippet:
class MailEvent(object):
def fire(self, instance, cls):
print "handling mail event from " + str(cls.__name__) + " " + str(instance.name)
class LogEvent(object):
def fire(self, instance, cls):
print "handling log event from " + str(cls.__name__) + " " + str(instance.name)
# using inheritance
class BaseWorker(object):
def __init__(self, name):
self.name = name
self.event_factory = {"mail":MailEvent(), "log":LogEvent()}
def event(self, evt):
if evt in self.event_factory:
self.event_factory[evt].fire(self, type(self))
class Worker(BaseWorker):
pass
worker = Worker("SIMPLE")
worker.event("mail")
worker.event("log")
|
In BaseWorker, we have an event_factory created and we fire one of the events in the factory when the method, event(), of BaseWorker is called. With event() defined in BaseWorker class, programmers can inherit it and get the power of event without any effort. However, there are some problems arising when event() is not the only operation in BaseWorker. As time goes by, other programmers might add more and more operations. Therefore BaseWorker became more and more unmaintainable and...ugly. :P
How to solve this issue? There is a simple solution: delegate the responsibilities to those classes who should care about it.
##### delegation approach #####
# naive way
class EventProxyA(object):
def __init__(self):
self.event_factory = {"mail":MailEvent(), "log":LogEvent()}
def handle_event(self, evt, instance, cls):
if evt in self.event_factory:
self.event_factory[evt].fire(instance, cls)
class BaseWorkerA(object):
def __init__(self, name):
self.name = name
self.event_proxy = EventProxyA()
def event(self, evt):
self.event_proxy.handle_event(evt, self, type(self))
class WorkerA(BaseWorkerA):
pass
worker = WorkerA("SIMPLE_A")
worker.event("mail")
worker.event("log")
|
The idea is very simple. We try to separate the work to another class and ask it to do what we need. We can see that BaseWorkerA cleaner than BaseWorker. You know this basic trick very well. Some people give this trick a name, delegation. But there is still a problem. If you see the implementation of BaseWorkerA, could you realize the event a delegation to another class object immediately? Try to imagine the delegation tricks and methods of BaseWorkerA mixed together (just as usual class). Could you really see what you want to describe after weeks? Not easy.
We can use Python's descriptor to make delegations much more obvious. :-)
# using descriptor
class EventProxyB(object):
def __init__(self):
self.event_factory = {"mail":MailEvent(), "log":LogEvent()}
def __get__(self, instance, cls):
def handle(evt):
if evt in self.event_factory:
# access caller's info without passing any parameter
self.event_factory[evt].fire(instance, cls)
return handle;
class BaseWorkerB(object):
event = EventProxyB()
def __init__(self, name):
self.name = name
class WorkerB(BaseWorkerB):
pass
worker = WorkerB("SIMPLE_B")
worker.event("mail")
|
Each time we access the event variable in BaseWorkerB, we would get a function object. From the view of users of BaseWorkerB, they can not distinguish the difference between methods and descriptors and the author of BaseWorkerB get much clearer for what are delegations and what are the methods of the BaseWorkerB really need to do. Neat, right?
留言
張貼留言