Статьи

Практический PHP-рефакторинг: извлечение суперкласса

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

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

Почему базовый класс?

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

Извлеченный суперкласс является абстрактным: обычно нет причин делать его конкретным.

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

меры

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

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

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

пример

Наша отправная точка — это пара не связанных между собой объектов презентации, Post и Link.

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

    public function testALinkShowsItsAuthor()
    {
        $link = new Link("/posts/php-refactoring");
        $this->assertEquals("<p><a href=\"/posts/php-refactoring\">/posts/php-refactoring</a></p>",
                            $link->toHtml());
    }
}

class Post
{
    public function __construct($text)
    {
        $this->text = $text;
    }

    public function toHtml()
    {
        return "<p>" . $this->text . "</p>";
    }
}

class Link
{
    public function __construct($href)
    {
        $this->href = $href;
    }

    public function toHtml()
    {
        return "<p><a href=\"" . $this->href . "\">" . $this->href . "</a></p>";
    }
}

Существует некое дублирование, видимое невооруженным глазом. Давайте сделаем некоторые предварительные шаги, чтобы сделать это более наглядным, например, объединяя их поля, называя их как $ this-> content.

class Post
{
    public function __construct($content)
    {
        $this->content = $content;
    }

    public function toHtml()
    {
        return "<p>" . $this->content . "</p>";
    }
}

class Link
{
    public function __construct($content)
    {
        $this->content = $content;
    }

    public function toHtml()
    {
        return "<p><a href=\"" . $this->content . "\">" . $this->content . "</a></p>";
    }
}

Мы также унифицируем метод toHtml (), извлекая другой бит:

class Post
{
    public function __construct($content)
    {
        $this->content = $content;
    }

    private function displayContent()
    {
        return $this->content;
    }

    public function toHtml()
    {
        return "<p>" . $this->displayContent() . "</p>";
    }
}

class Link
{
    public function __construct($content)
    {
        $this->content = $content;
    }

    private function displayContent()
    {
        return "<a href=\"$this->content\">$this->content</a>";
    }

    public function toHtml()
    {
        return "<p>" . $this->displayContent() . "</p>";
    }
}

Теперь мы можем извлечь пустой суперкласс:

abstract class ParagraphBox
{
}

class Post extends ParagraphBox
{
    public function __construct($content)
    {
        $this->content = $content;
    }

    private function displayContent()
    {
        return $this->content;
    }

    public function toHtml()
    {
        return "<p>" . $this->displayContent() . "</p>";
    }
}

class Link extends ParagraphBox
{
    public function __construct($content)
    {
        $this->content = $content;
    }

    private function displayContent()
    {
        return "<a href=\"$this->content\">$this->content</a>";
    }

    public function toHtml()
    {
        return "<p>" . $this->displayContent() . "</p>";
    }
}

Затем мы поднимаем все, что является общим для классов, с соответствующей видимостью (защищено). Мы также добавляем определение для поля содержимого $ this->, которое до сих пор игнорировалось и создавалось по мере необходимости. Испытания (не показаны) остаются такими же, как на первом этапе, так и проходными.

abstract class ParagraphBox
{
    protected $content;

    public function __construct($content)
    {
        $this->content = $content;
    }

    /**
     * @return string
     */
    abstract protected function displayContent();

    public function toHtml()
    {
        return "<p>" . $this->displayContent() . "</p>";
    }
}

class Post extends ParagraphBox
{
    protected function displayContent()
    {
        return $this->content;
    }

}

class Link extends ParagraphBox
{
    protected function displayContent()
    {
        return "<a href=\"$this->content\">$this->content</a>";
    }
}