I have two coupled classes DhcpServer and SessionManager. I got the following requirements in my specs that led to that coupling:
DhcpServermust not issue an IP address lease ifSessionManagerforbids that (e.g. an error occurred while creating a session)SessionManagermust start a session upon creation of a new lease byDhcpServerand destroy a session as soon as that lease expires or gets released explicitly by a client- On the other hand
DhcpServermust destroy the lease ifSessionManagerstopped 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
SessionManagercould create a session. Make theDhcpServerlisten 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.