Why you should be using interfaces

If you aren't already using interfaces, you're really missing out. They'll change your life, and maybe even for the better!

As a programmer, few things are more frustrating and time-consuming than retrofitting existing code. If you aren't lucky enough to work in one of the few organizations that have the magical combination of "infinite time and infinite money"; and as a result, projects never seem to be as thoroughly documented or tested as they should be. This may not be a problem in the short-term (i.e., while the project is fresh on everyone's minds) but requirements evolve and dependencies obsolesce. When the time eventually comes to swap components out, or if your assumptions turn out to be incorrect, how will you protect yourself from going back losing momentum while you revisit all of your previous work?

The answer: Interfaces.

Interfaces are programming tools provided by some object-oriented programming languages which allow programmers to define contracts between two (or more) pieces of code. The outward-facing parts of the interface define how the outside world will interact with a component. Meanwhile, the inward-facing parts of the interface define how the component interacts with the world.

As an example, every lock operates in essentially the same way. If you were picking a lock for a fence, your chief concern is probably related to whether the lock will fit in the gate. If the lock can't be used on the fence, then it doesn't fill your need — even if it's still technically a lock. Likewise, if it's impossible to disengage the lock, or if the lock disengages with any key, then it also fails to meet your requirements. Based on these requirements, we have defined the interface for the lock: the lock must fit in your fence, you must be able to unlock it with a key, and it must reject any key that isn't your key. Beyond these requirements, everything from color to brand is just personal preference.

At this point, you may be asking yourself why to use an interface at all instead of just using an abstract class. Indeed, some languages don't even support interfaces; and it's my opinion that they're lesser because of it. The main difference between an abstract class and an interface is in their core purpose. An abstract class defines a behavior, while an interface defines an expectation. An abstract class defines default behavior which child classes can extend, but an interface simply defines that any class which implements the abstract class must abide by these behaviors.

This distinction becomes extremely important in single-inheritance, loosely typed languages like PHP. Since a class can only inherit from one parent, it may not be practical or feasible to use an abstract class to ensure that certain default behavior exists. As a result, you may end up with classes that are technically interchangeable but only tangentially related. Using an interface here helps to enforce consistency between these classes without resorting to a God-Class.

To illustrate its usefulness, we can use an interchangeable database as an example.

Scenario

You have been assigned to a project where data must be read from (and written to) an external source. You cannot be certain what the exact source will be, but you do know that you will need to support PostgreSQL and MySQL databases, JSON and XML files, and MongoDB. Each system will fill the same technical role (date store), but the mechanics different enough that each should be implemented using a different class. Rather than spread these differences around the program, the lead developer on the project decides that these differences should be contained within the component. To facilitate this, an interface is defined which all of these classes must follow:

/**
 * A simplified generic database interface
 */
interface Database
{
    /**
     * Constructor function that prepares the database connection to be
     * opened or closed at will.
     *
     * @constructor
     * @param {array} $config   The configuration array may not be portable,
     *                          but this localizes the changes to resource-
     *                          acquisition instead of whenever a connection
     *                          is opened.
     */
    public function constructor($config);

    /**
     * Opens a connection to the database.
     *
     * @return {bool} Whether the connection was successfully opened.
     */
    public function open();

    /**
     * Closes the connection to the database.
     *
     * @return {bool} Whether the connection was successfully closed.
     */
    public function close();

    /**
     * Retrieves a record from the database at index "id".
     *
     * @param  {string} $resource The resource to read from
     * @param  {mixed}  $id       The ID to read from
     * @return {array}            The content of the record at index "id"
     */
    public function getRecord($resource, $id);

    /**
     * Saves a record with contents "data" into the database at index "id".
     *
     * @param  {string} $resource The resource to write to
     * @param  {mixed}  $id       The ID to write to
     * @param  {array}  $data     The content to write to the database
     * @return {bool}             Whether the write operation succeeded
     */
    public function setRecord($resource, $id, array $data);
}

This interface gives us all of the generic input and output operations you need to read and write to or from a database and interact with objects which implement the interface using a generic format. So long as the XML file, MySQL database, and MongoDB database can have information converted to/from their native forms into an array, they will be interchangeable.

The Payoff

The immediate benefit of using an interface for this problem would be that you could swap any of these databases at any point in your code for any other database. If you were so inclined, you could take this a step further and define which class to use in a configuration file so that the database could be swapped without ever having to touch the source-code. This could be handy in an instance where a database does not yet exist or has not yet been populated with data, and so dummy-data is being stored in a JSON file. The system could read from the JSON file, and then swap to a database later in the project without needing to rewrite any existing code.

The long-term benefits of using an interface are that they offer you the flexibility to create new implementations and that they enforce consistency if you need to change an interface.

When a class with an interface is instantiated, the class is checked to ensure it fully implements that interface. Therefore, if an interface is updated (but an implementation is not) then the class will throw an error. If an interface is used in conjunction with unit-testing, these discrepancies can be made obvious by the error-messages generated during testing rather than in the complaints generated by customers.

Further, if a new implementation must be introduced, there is already a blueprint for how the class must behave. This is particularly useful for when new technologies are introduced (e.g. an upgrade to PHP7) or when new requirements arise (e.g. the customer unexpectedly announces a need to use DynamoDB instead of MongoDB):

$ddb = new DynamoDB([ /* config */ ]);
$ddb->getRecord('my-table', 'id', 7); // matches field "id = 7" in a PostgreSQL database

$xmldb = new XmlDatabase([ /* config */ ]);
$xmldb->getRecord('my-table', 'id', 7); // matches XPath "/my-table/id/7" in an XML document

Summary

If you're in a language that supports interfaces, and you aren't already using them, you're really missing out. These are not a PHP-exclusive technique — they also exist in Java —. As a developer who migrated from C++ into PHP, I didn't realize how great they were until I started using them; and now I wouldn't have it any other way.

Comments

Back to top