Статьи

Практический рефакторинг PHP: Свернуть иерархию

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

Collapse Иерархия рефакторинга предоставляет ряд шагов для объединения двух таких классов в один, что упрощает конструкцию.

Зачем удалять класс?

Конструкцию, в которой меньше движущихся частей, проще понять и немного сложнее сломать. Дополнительный объект, который не представляет концепцию домена и не обеспечивает ценность путем устранения дублирования, просто добавляет случайную сложность в проект.

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

Предположение этого рефакторинга состоит в том, что в иерархии нет других подклассов; если они есть, теперь они должны расширить объединенный класс, но вам следует искать возможные нарушения принципа подстановки Лискова . Например, в транспортной области некорректно объединять класс Vehicle в его Coach подкласса, в то время как в результате родственный класс Car расширяет Coach.

меры

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

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

пример

В этом примере мы работаем со знакомым классом NewsFeedItem и его подклассом Link. Требования к ним немногочисленны, и никаких других подклассов не задействовано; на самом деле сам по себе NewsFeedItem не нужен, и он также может быть абстрактным.

<?php
class CollapseHierarchy extends PHPUnit_Framework_TestCase
{
    public function testALinkShowsItsAuthor()
    {
        $link = new Link("/posts/php-refactoring", "giorgiosironi");
        $this->assertEquals("<a href=\"/posts/php-refactoring\">/posts/php-refactoring</a> -- @giorgiosironi",
                            $link->toHtml());
    }
}

class NewsFeedItem
{
    protected $content;
    protected $author;

    public function __construct($content, $author)
    {
        $this->content = $content;
        $this->author = '@' . ltrim($author, '@');
    }

    /**
     * @return string   an HTML printable version
     */
    public function __toString()
    {
        return "$this->content -- $this->author";
    }
}

class Link extends NewsFeedItem
{
    public function toHtml()
    {
        return "<a href=\"$this->content\">$this->content</a> -- $this->author";
    }
}

Мы решили удалить суперкласс. Мы нажимаем все вниз, но мы должны искать ссылки, такие как new NewsFeedItem (…), чтобы исправить это, прежде чем выполнить этот шаг.

class NewsFeedItem
{
}

class Link extends NewsFeedItem
{
    protected $content;
    protected $author;

    public function __construct($content, $author)
    {
        $this->content = $content;
        $this->author = '@' . ltrim($author, '@');
    }

    /**
     * @return string   an HTML printable version
     */
    public function __toString()
    {
        return "$this->content -- $this->author";
    }
    public function toHtml()
    {
        return "<a href=\"$this->content\">$this->content</a> -- $this->author";
    }
}

Теперь мы можем удалить суперкласс вместе с ключевым словом extends в Link.

class Link
{
    protected $content;
    protected $author;

    public function __construct($content, $author)
    {
        $this->content = $content;
        $this->author = '@' . ltrim($author, '@');
    }

    /**
     * @return string   an HTML printable version
     */
    public function __toString()
    {
        return "$this->content -- $this->author";
    }
    public function toHtml()
    {
        return "<a href=\"$this->content\">$this->content</a> -- $this->author";
    }
}