Статьи

Практический рефакторинг PHP: поле Pull Up

Мы сейчас входим на территорию обобщений. Это естественный процесс, поскольку мы добавляем все больше и больше тестов, чтобы наш код стал более общим; но после того, как мы прошли наш тестовый этап, этап рефакторинга включает в себя множество способов справиться с обобщением.

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

Я не должен напоминать вам, что дублирование кода повсеместно признается как рак, потому что множество мест, где выражается концепция (или правило, стратегия, требование), могут легко выйти из синхронизации: одна из сущностей может быть обновлена а другой нет.

Первый рефакторинг обобщения, с которым мы сталкиваемся, это Pull Up Field : он нацелен на сценарий, в котором несколько подклассов имеют одинаковое поле, или очень похожее поле, которое с изменением имени может стать идентичным другим копиям. Решение, предложенное этим рефакторингом (ни в коем случае не уникальным), состоит в том, чтобы переместить поле вверх в иерархию, чтобы оно наследовалось подклассами.

Зачем двигаться вверх по иерархии?

С внешней точки зрения ничего не изменится : вызовы методов будут точно такими же, но классы будет проще поддерживать, так как нужно изменить только одно определение поля. Меньше кода для перемещения, удаления и обновления.

Если вы применяете TDD и 4 правила простого проектирования, вы можете начать с решения, содержащего дублирование, и даже иногда копировать и вставлять код (имея в виду, что большая его часть изменится в новом классе или методе). Но перед следующим коммитом вам нужно будет максимально уменьшить дублирование.

Этот рефакторинг позволяет также впоследствии перемещать методы, используя общее поле: это фактически является обязательным условием для некоторых экземпляров Pull Up Method.

меры

  1. Проверьте использование поля: если вы оставляете поля закрытыми или защищенными, вы знаете, что у вас будет ограниченный набор исходных файлов, где можно искать и выполнять операцию поиска. Все операции, выполняемые со значением поля или со значением поля, должны учитываться.
  2. В случае необходимости переименуйте поля в один и тот же идентификатор (это старое название школы для имен, ссылающихся на переменные и поля).
  3. Создайте поле в суперклассе , которое пока будет переопределено полями подклассов.
  4. Удалите поля подкласса .

Обратите внимание, что если в поднятом поле сохранено исходное имя, между шагами 3 и 4 вы можете получить ошибочные тесты, если их область действия изначально была закрытой. Можно переопределить поля в PHP, но не с более строгой видимостью. Тесты, однако, не изменятся на любом этапе.

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

пример

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

<?php
class PullUpField extends PHPUnit_Framework_TestCase
{
    public function testAPostShowsItsAuthor()
    {
        $post = new Post("Hello, world!", "giorgiosironi");
        $this->assertEquals("Hello, world! -- giorgiosironi",
                            $post->__toString());
    }

    public function testALinkShowsItsAuthor()
    {
        $link = new Link("http://en.wikipedia.com", "giorgiosironi");
        $this->assertEquals("<a href=\"http://en.wikipedia.com\">http://en.wikipedia.com</a> -- giorgiosironi",
                            $link->__toString());
    }
}

abstract class NewsFeedItem
{
}

class Post extends NewsFeedItem
{
    private $text;
    private $author;

    public function __construct($text, $author)
    {
        $this->text = $text;
        $this->author = $author;
    }

    public function __toString()
    {
        return "$this->text -- $this->author";
    }
}

class Link extends NewsFeedItem
{
    private $url;
    private $author;

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

    public function __toString()
    {
        return "<a href=\"$this->url\">$this->url</a> -- $this->author";
    }
}

Since the fields already have the same name, we don’t have to modify them. We just add a field in the protected scope of the superclass. We can specify a common documentation for all the classes that inherit from it:

abstract class NewsFeedItem
{
    /**
     * @var string  references the author's Twitter username
     */
    protected $author;
}

To make the tests pass again, we should eliminate the fields from the subclasses:

class Post extends NewsFeedItem
{
    private $text;

    public function __construct($text, $author)
    {
        $this->text = $text;
        $this->author = $author;
    }

    public function __toString()
    {
        return "$this->text -- $this->author";
    }
}

class Link extends NewsFeedItem
{
    private $url;

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

    public function __toString()
    {
        return "<a href=\"$this->url\">$this->url</a> -- $this->author";
    }
}

There are no duplicated fields now, so this refactoring stops here. In the next episodes of this series we will remove even more duplication (but not necessarily get fewer lines of code).