I have two coupled classes DhcpServer
and SessionManager
. I got the following requirements in my specs that led to that coupling:
DhcpServer
must not issue an IP address lease if SessionManager
forbids that (e.g. an error occurred while creating a session)SessionManager
must start a session upon creation of a new lease by DhcpServer
and destroy a session as soon as that lease expires or gets released explicitly by a clientDhcpServer
must destroy the lease if SessionManager
stopped a corresponding session (e.g. by sysadmin's request)At first it was tempting to put all the code into a single class. But the responsibilities were distinct, so I split them into two and created two interfaces:
class ISessionObserver(object):
def onSessionStart(**kwargs): pass
def onSessionStop(**kwargs): pass
class IDhcpObserver(object):
def onBeforeLeaseCreate(**kwargs):
"""
return False to cancel lease creation
"""
pass
def onLeaseCreate(**kwargs): pass
def onLeaseDestroy(**kwargs): pass
Then I implemented IDhcpObserver
in SessionManager
and ISessionObserver
in DhcpServer
. And that led to coupling. Even though the classes do not depend on each other directly they do depend on the interfaces declared in each other's packages.
Later I want to add another protocol for session initiation leaving SessionManager
's logic intact. I don't want it to implement IAnotherProtocolObserver
as well.
Also DHCP server as such has nothing to do with my notion of session. And since there's no DHCP protocol implementation for Twisted (which I'm using) I wanted to release it as a separate project that has no dependencies neither on SessionManager
nor on its package.
How can I satisfy my spec requirements while keeping the code pieces loosely coupled?
A good way to decouple classes is to use events.
So what you need to do is to "fire" events when something happens. Example: Send an event "session created" when the SessionManager
could create a session. Make the DhcpServer
listen for that event and prepare a lease when it receives it.
Now all you need is a third class which creates the other two and configures the event listeners.
The beauty of this solution that it keeps everything simple. When you write unit tests, you will always only need one of the classes because all you need is to check whether the correct event has been fired.