Статьи

Практический рефакторинг PHP: метод шаблона формы

Дублирование не всегда выражается в виде идентичного блока кода: часто его легче обнаружить, поскольку оно существует на более высоком уровне абстракции.

Рассмотрим алгоритм сортировки, классический пример в области компьютерных наук: он может быть реализован в самых разных структурах данных, если их элемент можно сравнивать и менять местами. Если мы сравним быструю сортировку, реализованную в массиве целых чисел или в SplLinkedList, содержащем объекты, мы увидим тот же алгоритм.

Form Template Method предназначен для создания общих блоков кода путем извлечения различных блоков в методы с одинаковой сигнатурой; в нашем примере операции сравнения и замены объектов, подобных коллекции. Общие блоки кода затем могут быть перенесены в подкласс.

Почему метод шаблона?

Устранение дублирования выходит за рамки таргетинга сразу видимых случаев . Частный случай, с которым мы сталкиваемся с помощью этого рефакторинга, — это методы, выполняющие те же самые высокоуровневые шаги , но с некоторыми различиями в нескольких строках.

Результатом рефакторинга является то, что общий код перемещается в суперклассе как шаблонный метод; реализация шагов оставлена ​​для подклассов как методы ловушки. Они обычно определяются как абстрактные в суперклассе, который знает об их существовании, но не об их реализациях. В PHP не обязательно определять абстрактные методы ловушек из-за отсутствия статических проверок, но это желательно, поскольку он улавливает потенциальную фатальную ошибку во время загрузки, а не во время выполнения.

Шаблонный метод основан на наследовании , поэтому клиентскому коду менять не придется. Этот рефакторинг является применением принципа Open / Closed: будет легко добавить новые версии алгоритма, начиная с Template Template, просто добавив новый подкласс.

меры

  1. Разложите похожие методы : извлеките только разные небольшие блоки кода в методы с одинаковой сигнатурой. Фаулер предполагает, что после этого шага каждая пара аналоговых методов в подклассах должна быть либо одинаковой (шаблонный метод), либо совершенно разной (методы ловушки).
  2. Если существуют разные методы только для имени вызываемых ими методов , переименуйте разные методы ловушек, которые они вызывают, с тем же именем и подписью.
  3. При необходимости используйте Extract Superclass и Pull Up Method, чтобы переместить идентичные методы в него.
  4. Определите сигнатуры методов подключения как абстрактные , чтобы гарантировать, что они определены в каждом подклассе.

пример

В исходном состоянии два класса VideoTweet и ArticleTweet представляют текст для твита, связанного с видео, или статьи на 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";
    }
}

Предварительным шагом является извлечение общих частей в суперкласс с помощью Extract Superclass , Pull Up Field и 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";
    }
}

Теперь мы начинаем этот рефакторинг. __toString () станет нашим методом шаблона, и мы должны извлечь разные биты в методы с общей сигнатурой.
Эти методы ловушек отличаются, в то время как __toString () теперь одинакова для всех (двух) подклассов:

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";
    }
}

Мы поднимаем __toString (), чтобы устранить его дублирование:

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";
    }
}

Мы также определяем методы перехвата как абстрактные: новые классы должны будут обеспечить их присутствие, прежде чем их вообще можно будет создать. Также легче определить новый тип твитов, так как они просто принимают небольшой метод prefix () вместо новых копий __construct () и __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();
}