Monday, June 30, 2008

Interfaces in Python.

Defining interfaces that your classes will provide is an essential programming practice if you plan to develop extensible code. That is, when you define interfaces, you are defining your internal architecture. The interfaces you define will give you a starting point in which you can discover more meaningful abstractions in your application domain.

Interfaces are not a replacement for object-oriented analysis. You can't just put together a set of interfaces that you think might work and hope for the best. Rather, your interfaces should be derived from concepts created by doing analysis and development. That being said, you will discover how your interfaces need to be refined and expanded upon as you are implementing classes that realize your interfaces. Don't get stuck in the waterfall approach to software development.

Zope provides an interface package which allows Python developers to define interfaces and implement classes that realize them. For example, say I want to define a blog entry interface. Using the zope.interface package, I might do something similar to:

import zope.interface

class IBlogEntry(zope.interface.Interface):
"""A simple blog account interface."""
title = zope.interface.Attribute("""The title of the blog entry.""")
content = zope.interface.Attribute("""The main content of the blog entry.""")
date = zope.interface.Attribute("""The publish date of the blog entry.""")

def setTitle(self, title):
"""Set the title of the blog entry."""

def setContent(self, content):
"""Set the content of the blog entry."""

def setDate(self, date):
"""Set the date of the blog entry."""

def getTitle(self):
"""Return the title of the blog entry."""

def getContent(self):
"""Return the content of the blog entry."""

def getDate(self):
"""Return the date of the blog entry."""

def publish(self):
"""Publish the blog entry."""

class BlogEntry:
zope.interface.implements(IBlogEntry)
And of course we would then have to implement all the attributes and methods defined in IBlogEntry. We can then test if IBlogEntry is implemented by any given BlogEntry instance as follows:

blog_entry_obj=BlogEntry()
IBlogEntry.providedBy(blog_entry_object)

Of course, this interface gives us a good idea of what an blog entry implementation might look like. We can easily implement a different blog entry class because the interface provides us with an interchangeable template. As long as these methods an attributes are implemented, our class may be used where the IBlogEntry interface is required.

Is there a way we could re-factor our interface to get more use out of it? I think so. In a system which incorporates blog entry abstractions, it is most probable that there will be similar abstractions. Similar in the sense that these other abstractions will have a title, content, and date. Lets take a look at an alternative interface implementation of interfaces provided by BlogEntry.


import zope.interface

class ITitle(zope.interface.Interface):
"""A simple title interface for abstractions with a title."""
title = zope.interface.Attribute("""The title of the abstraction.""")

def setTitle(self, title):
"""Set the title of the abstraction."""

def getTitle(self):
"""Return the title of the abstraction."""

class IContent(zope.interface.Interface):
"""A simple content interface for abstractions with content."""
content = zope.interface.Attribute("""The content of the abstraction.""")

def setContent(self, content):
"""Set the content of the blog entry."""

def getContent(self):
"""Return the content of the blog entry."""

class IDate(zope.interface.Interface):
"""A simple date interface for abstractions with dates."""
date = zope.interface.Attribute("""The date of the abstraction.""")

def setDate(self, date):
"""Set the date of the blog entry."""

def getDate(self):
"""Return the date of the blog entry."""

class IPublish(zope.interface.Interface):
"""A simple publishing interface for publishing abstractions."""

def publish(self):
"""Publish the abstractions."""

class BlogEntry:
zope.interface.implements(ITitle)
zope.interface.implements(IContent)
zope.interface.implements(IDate)
zope.interface.implements(IPublish)

In this implementation, we have removed all references to blog entry in the interfaces we have defined. This is a good practice when there are several non-trivial abstractions involved in the system being implemented. We can then reuse our interfaces for several class implementations rather than keeping them specific to blog entry.

Finally, keep your interfaces simple. The goal in using interfaces is to keep the implementation of abstractions consistent. This is not accomplished be further complicating the code by adding interfaces that are more complex than necessary.