I am a programmer and architect (the kind that writes code) with a focus on testing and open source; I maintain the PHPUnit_Selenium project. I believe programming is one of the hardest and most beautiful jobs in the world. Giorgio is a DZone MVB and is not an employee of DZone and has posted 636 posts at DZone. You can read more from them at their website. View Full User Profile

Practical PHP Refactoring: Extract Interface

01.18.2012
| 4385 views |
  • submit to reddit

A concrete class still defines an implicit interface by itself, as the set of its public methods. When the called interface is a subset of this, or it is depended upon in multiple places, it is interesting to make it explicit.

The Extract Interface refactoring creates an interface from an existing concrete class.

Why?

Interfaces are not strictly necessary for computation: PHP is already Turing-complete without them. Interfaces describe what a collaborator should accomplish, while classes describe how, with all the necessary code. However, apart from documenting a contract between two classes, they have several benefits.

In the realm of TDD, they enable outside-in testing with mocks: this means you can test-driver a class together with the interfaces it depends upon, even if the real implementation of its collaborators do not exist, or do not implement them, yet.

Outside-in TDD helps shaping interfaces from the point of view of the caller instead of the callee. As such, these interfaces should reside in a separate folder/namespace from the implementing code (for the Dependency Inversion Principle).

What if a class has multiple calling points? It can possibly expose multiple interfaces, where every client depends exactly at most one of them and not on any additional methods.

Steps

  1. Create an empty interface. If you can only think of a name such as Set and ISet for a class and interface, start by saving the good name for the interface: Set and TreeBasedSet is better than the former case.
  2. Declare common operations in the interface, with method signatures identical to the original. In case only some operations are called by the client, only this subset should be copied in the interface.
  3. Add implements keywords to tie existing concrete classes to the interface.
  4. Simplify the client code by making it dependent on the interface where possible.

Examples of the last step are multiple:

  • the tests can now use a mock or a stub easily (even coded by hand instead of a generated one), since starting from interface you'll have a small set of methods to override.
  • Type hints can be written referring to the interface name whenever the client code calls only the methods listed in the interface.
There are also many possible subsequent refactorings, enabled by the presence of the interface:
  • renaming the interface and the concrete classes to reflect a role (in the interface) and implementation peculiarities (in the concrete classes).
  • Add or drop methods in the contract to fit the desires of the caller.
  • Extract some functionality into a Decorator or a Composite, which are multiple implementation of an interface.

Example

In the initial state, the Money presenter object is depending on a concrete class, EuroLocale.

<?php
class ExtractInterface extends PHPUnit_Framework_TestCase
{
    public function testShouldDisplayAMoneyAmount()
    {
        $locale = new EuroLocale();
        $money = new Money("42");
        $this->assertEquals("42 €", $money->display($locale));
    }
}

class EuroLocale
{
    public function format($amount)
    {
        return $amount . ' €';
    }
}

class Money
{
    private $amount;

    /**
     * @param string $amount    to keep precision
     */
    public function __construct($amount)
    {
        $this->amount = $amount;
    }

    public function display(EuroLocale $locale)
    {
        return $locale->format($this->amount);
    }
}

We create an interface, Locale, and just the single public method is extracted. Anything more would not be shown in this example, and won't be brought up into the interface.

interface Locale
{
    /**
     * @return string
     */
    public function format($amount);
}

We add an implements keyword, and simplify the type hint dependency to target just Locale.

interface Locale
{
    /**
     * @return string
     */
    public function format($amount);
}

class EuroLocale implements Locale
{
    public function format($amount)
    {
        return $amount . ' €';
    }
}

class Money
{
    private $amount;

    /**
     * @param string $amount    to keep precision
     */
    public function __construct($amount)
    {
        $this->amount = $amount;
    }

    public function display(Locale $locale)
    {
        return $locale->format($this->amount);
    }
}

Usually a dependency on a concrete class, which may have a dozen different methods, does not let us refactor tests introducing Test Doubles. This happens because we are unsure about which methods we should redefine: which are called in this test method? And by Money objects in general?

Now that we have an interface, we define explicitly that only format() is called, even if EuroLocale may have many others. So we can break the tests in two, one targeting Money and the other EuroLocale. Note the order of the unit tests: they are completely independent, so we can test (and thus develop) Money first.

<?php
class ExtractInterface extends PHPUnit_Framework_TestCase
{
    public function testShouldFormatItsAmountBeforeDisplayingIt()
    {
        $locale = $this->getMock('Locale');
        $locale->expects($this->once())->method('format')->with("42")->will($this->returnValue('42 SIMBOL'));
        $money = new Money("42");
        $this->assertEquals("42 SIMBOL", $money->display($locale));
    }

    public function testShouldFormatAnAmountWithTheEuroSighn()
    {
        $locale = new EuroLocale();
        $this->assertEquals("42 €", $locale->format("42"));
    }
}

In the real world, there would have been dozens of tests involving both objects, with a lot of setup code and machinery. Interfaces let us break direct dependencies and test classes in real isolation, an approach that scales better to many tests. For example, in the final version every test for a new formatting options (like 42.00 or 10,000.00 EUR) needs only to create a string instead of a Money object. Equivalently, any test for a new user of format() does not have to care about particular formatting rules.

Published at DZone with permission of Giorgio Sironi, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Filippo Tessarotto replied on Wed, 2012/01/18 - 6:54am

I consider this refactoring one of the most important:

  1. Separate, for real, tests of the caller from tests of the callee, resulting in a real testing isolation
  2. Clarify what caller needs
  3. Clarify what caller does not need
  4. Clarify what callee has to expose
  5. Clarify what callee can jealously hide to the world

Where "clarify" refers both to the code user and the code developer

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.