Статьи

Практический рефакторинг PHP: замените делегирование наследованием

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

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

Зачем двигаться в сторону наследования?

Фаулер называет рост кода делегирования силой, требующей такого рефакторинга. Не все проблемы можно точно смоделировать с помощью делегирования, хотя это эквивалентно наследованию. В зависимости от домена и сущности, вы можете предпочесть отношения наследования Vehicle ^ -Coach между классами (где Vehicle содержит поле типа « водитель» ) или Coach — *> «Состав водителя».

Есть и другие причины для выбора одного над другим, например изменяемое состояние в соавторе . В случае наследования он не может быть разделен между объектами, в то время как в делегировании он может.

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

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

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

меры

Шаги выполняют обратную процедуру Заменить Наследование с Делегированием .

  1. Заставьте объект клиента расширять класс делегата .
  2. Делегат должен стать $ это путем инициализации в конструкторе клиента.
  3. Удалить делегирование : методы теперь должны быть унаследованы; меняйте один метод за раз, если вы не уверены в эффектах.
  4. Удалите поле делегата и удалите ненужные косвенные указания.

пример

Мы начинаем с конца предыдущего примера, но он привел нас в нужное русло. Большая часть состояния была перенесена в формат из-за желания устранить дублирование полей в подклассы (текст / контент, автор). Кстати, это уже не формат, но начинает появляться имя, подобное FeedItem …
__toString () — это пример метода, непосредственно делегированного объекту формата, но в реальном коде их может быть (и будет) гораздо больше ,

<?php
class ReplaceDelegationWithInheritance 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());
    }
}

class TextSignedByAuthorFormat
{
    private $text;
    private $author;

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

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

class Post
{
    private $format;

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

    public function __toString()
    {
        return $this->format->__toString();
    }
}

class Link
{
    private $format;

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

    protected function displayedText($url)
    {
        return "<a href=\"$url\">$url</a>";
    }

    public function __toString()
    {
        return $this->format->__toString();
    }
}

Расширяя класс делегата, мы доказываем, что он совместим с будущим подклассом:

class TextSignedByAuthorFormat
{
    private $text;
    private $author;

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

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

class Post extends TextSignedByAuthorFormat
{
    private $format;

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

    public function __toString()
    {
        return $this->format->__toString();
    }
}

class Link extends TextSignedByAuthorFormat
{
    private $format;

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

    protected function displayedText($url)
    {
        return "<a href=\"$url\">$url</a>";
    }

    public function __toString()
    {
        return $this->format->__toString();
    }
}

Мы модифицируем __construct () и удаляем делегирование в методах. Если мы этого не сделаем, вызов будет повторяться бесконечно, так как $ this-> method () вызовет $ this-> format-> method (), который на самом деле является $ this-> method (), и снова начнет цикл. Xdebug обнаружит эти проблемы и заблокирует скрипт на максимальном уровне вложенности.

class Post extends TextSignedByAuthorFormat
{
    private $format;

    public function __construct($text, $author)
    {
        parent::__construct($text, $author);
    }
}

class Link extends TextSignedByAuthorFormat
{
    private $format;

    public function __construct($url, $author)
    {
        parent::__construct($this->displayedText($url), $author);
    }

    protected function displayedText($url)
    {
        return "<a href=\"$url\">$url</a>";
    }
}

Теперь мы можем упростить, удалив поле делегата, переименовав суперкласс и удалив конструкторы, которые просто делегируют. Опять же, делегирующие методы должны были быть удалены на предыдущем шаге, если только они не заменили $ this-> format-> на parent :: , которые делегирующие методы никогда не используют вместо этого.

class FeedItem
{
    private $text;
    private $author;

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

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

class Post extends FeedItem {}

class Link extends FeedItem
{
    public function __construct($url, $author)
    {
        parent::__construct($this->displayedText($url), $author);
    }

    protected function displayedText($url)
    {
        return "<a href=\"$url\">$url</a>";
    }
}