Agile Zone is brought to you in partnership with:

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 637 posts at DZone. You can read more from them at their website. View Full User Profile

Practical PHP Refactoring: Form Template Method

01.25.2012
| 5647 views |
  • submit to reddit

Duplication is not always expressed as an identical block of code: often it is subtler to discover, because it exists at an higher level of abstraction.

Consider a sorting algorithm, a classic example in computer science: it can be implemented on widely different data structures, as long as their element can be compared and swapped. If we compare quicksort implemented on an array of integers or on a SplLinkedList containing objects, we see the same algorithm.

Form Template Method is about producing common blocks of code by extracting different blocks into methods with the same signature; in our example, the compare and swap operations of a collection-like objects. The common blocks of code can then be pulled up in a subclass.

Why a Template Method?

Eliminating duplication goes beyond targeting the immediately visible cases. The particular case we are tackling with this refactoring is that of methods performing the same higher-level steps, but with some differences in a few lines.

The result of the refactoring is that common code is moved in the superclass, as a Template Method; the implementation of the steps is left to the subclasses as hook methods. They are usually defined as abstract in the superclass, which knows of their existence but not about their implementations. In PHP it's not strictly necessary to define abstract hook methods due to the lack of static checks, but it is desirable since it catches a potential fatal error at loading time instead of at runtime.

Template Method is based on inheritance, so the client code won't have to change. This refactoring is an application of the Open/Closed Principle: it will be easy to add new versions of the algorithm starting from the Template Method, by simply adding a new subclass.

Steps

  1. Decompose the similar methods: extract only the different small blocks of code into methods with the same signature. Fowler suggests that after this step each pair of analogue methods in the subclasses should be either identical (Template Method) or completely different (hook methods).
  2. If there are methods different just for the name of the method calls they make, rename the different hook methods they call with the same name and signature.
  3. Use Extract Superclass if needed, and Pull Up Method to move the identical methods up in it.
  4. Define signatures of the hook methods as abstract, to ensure they are defined in each subclass.

Example

In the initial state, two classes VideoTweet and ArticleTweet are representing the text for a tweet linking to a video, or an article on DZone.

<?php
class FormTemplateMethod extends PHPUnit_Framework_TestCase
{
    public function testAVideoTweet()
    {
        $link = new VideoTweet('http://www.youtube.com/watch?...', "Lolcats");
        $this->assertEquals('Check out this video: Lolcats http://www.youtube.com/watch?...',
                            $link->__toString());
    }

    public function testAnArticleTweet()
    {
        $link = new ArticleTweet('http://css.dzone.com/category/tags/practical-php-refactoring', "Practical PHP Refactoring");
        $this->assertEquals('RT @DZone: Practical PHP Refactoring http://css.dzone.com/category/tags/practical-php-refactoring',
                            $link->__toString());
    }
}

class VideoTweet
{
    private $url;
    private $title;

    public function __construct($url, $title)
    {
        $this->url = $url;
        $this->title = $title;
    }

    public function __toString()
    {
        return "Check out this video: $this->title $this->url";
    }
}

class ArticleTweet
{
    private $url;
    private $title;

    public function __construct($url, $title)
    {
        $this->url = $url;
        $this->title = $title;
    }

    public function __toString()
    {
        return "RT @DZone: $this->title $this->url";
    }
}

A preliminary step is to extract the common parts into a superclass with Extract Superclass, Pull Up Field and Pull Up Constructor Body.

class Tweet
{
    protected $url;
    protected $title;

    public function __construct($url, $title)
    {
        $this->url = $url;
        $this->title = $title;
    }
}

class VideoTweet extends Tweet
{
    public function __toString()
    {
        return "Check out this video: $this->title $this->url";
    }
}

class ArticleTweet extends Tweet
{
    public function __toString()
    {
        return "RT @DZone: $this->title $this->url";
    }
}

Now we begin this refactoring. __toString() will become our Template Method, and we have to extract the different bits into methods with a common signature.
These hook methods are different, while __toString() is now the same for all (two) subclasses:

class VideoTweet extends Tweet
{
    public function __toString()
    {
        return $this->prefix() . ": $this->title $this->url";
    }

    protected function prefix()
    {
        return "Check out this video";
    }
}

class ArticleTweet extends Tweet
{
    public function __toString()
    {
        return $this->prefix() . ": $this->title $this->url";
    }

    protected function prefix()
    {
        return "RT @DZone";
    }
}

We pull up __toString() to eliminate its duplication:

class Tweet
{
    protected $url;
    protected $title;

    public function __construct($url, $title)
    {
        $this->url = $url;
        $this->title = $title;
    }

    public function __toString()
    {
        return $this->prefix() . ": $this->title $this->url";
    }
}

class VideoTweet extends Tweet
{
    protected function prefix()
    {
        return "Check out this video";
    }
}

class ArticleTweet extends Tweet
{
    protected function prefix()
    {
        return "RT @DZone";
    }
}

We also define the hook methods as abstract: new classes will have to ensure their presence before they can be instantiated at all. It's also easier to define new type of tweets, since they just take a little prefix() method instead of new copies of __construct() and __toString().

abstract class Tweet
{
    protected $url;
    protected $title;

    public function __construct($url, $title)
    {
        $this->url = $url;
        $this->title = $title;
    }

    public function __toString()
    {
        return $this->prefix() . ": $this->title $this->url";
    }

    /**
     * @return string
     */
    protected abstract function prefix();
}
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

David Weinraub replied on Wed, 2012/01/25 - 6:06am

I have used this often in the past, but always felt that my implementation was unsatisfactory. Now I see why: I failed to define the hook methods as abstract in the parent.

 As you note, any failure of a concrete subclass to implement the hook method now throws an error at load-time rather than at run-time. Much clearer now.

Thanks, Giorgio. ;-)

Giorgio Sironi replied on Sun, 2012/01/29 - 8:30am in response to: David Weinraub

Thanks for the feedback. Compile-time checks are not PHP's killer feature, but are always handy.

Comment viewing options

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