После Pull Up Field и Pull Up Method мы рассмотрим сегодня последний из этой категории рефакторингов: Pull Up Constructor Body.
Сценарий состоит из ряда конструкторов, которые содержат дубликаты и распределяются среди нескольких классов, имеющих общего предка. Решение, предлагаемое этим рефакторингом, состоит в том, чтобы вытянуть тело конструктора или его часть, чтобы устранить дублирование.
Почему конструктор — это не просто метод?
Этот рефакторинг отличается от Pull Up Method, потому что он использует особую семантику, такую как вызовы parent . Более того, в некоторых языках конструкторы не наследуются; в PHP они есть, поэтому аспект наследования не важен.
Разница между извлечением метода и конструктора заключается в том, что в последнем случае существует стандартный способ разделения общего кода (для подтягивания) и конкретного (для оставления в подклассах). Стандартная форма — родительский конструктор, плюс переопределяющий конструктор, который вызывает parent :: __ construct () , скрывая его в процессе. Существуют альтернативы переопределению, такие как абстрактные методы init (), но они не фиксируют все дополнительные особые параметры, которые могут понадобиться во время создания экземпляра.
Например, PHPUnit использует этот вариант с методами setUp (), которые вызываются родительским классом и обрабатывают настройку теста. Однако вы ничего не можете передать извне, так как setUp () не должен принимать аргументов. Это не проблема в данном конкретном случае, потому что вы никогда не будете создавать экземпляры тестовых примеров самостоятельно, но в целом вы создадите множество объектов, и поэтому конструктор подклассов должен быть свободным, чтобы требовать дополнительных аргументов.
Фактически, переопределение конструкторов — это специальная операция, так как вы можете переопределить все параметры и их число или порядок, добавить или удалить подсказки типа и указать значения по умолчанию. Вы не можете сделать это обычными методами:
PHP Fatal error: Declaration of Subclass::method() must be compatible with that of Superclass::method() in ...
Как и во всех рефакторингах Pull Up, благодаря наследованию клиентский код не должен изменяться.
меры
Обратите внимание, что код для подтягивания должен быть перемещен в начале конструкторов. Обычно конструктор представляет собой набор независимых инструкций, поэтому проблем не будет; если вы разделяете конструкцию и бизнес-логику, у вас не будет трудностей.
- Определите конструктор в суперклассе (изначально пустой и не вызванный).
- Переместите общий код , который должен быть в начале конструктора, вверх в суперклассе.
- Вызовите конструктор суперкласса в качестве первого шага каждого конструктора подкласса.
Это соглашение C ++, чтобы сохранить вызов родительского конструктора в качестве первого шага (в C ++ по умолчанию вызов выполняется автоматически.) Деструкторы, которые используются редко, имеют противоположную семантику.
пример
Мы перезапускаем с конца предыдущего рефакторинга , но изменение требований сделало дублирование при инициализации поля автора более очевидным. Это просто стандартный код для гарантии того, что @ стоит перед именами авторов:
<?php class PullUpConstructorBody 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 { /** * @var string references the author's Twitter username */ protected $author; /** * @return string an HTML printable version */ public function __toString() { return $this->displayedText() . " -- $this->author"; } /** * @return string */ protected abstract function displayedText(); } class Post extends NewsFeedItem { private $text; public function __construct($text, $author) { $this->text = $text; $this->author = '@' . ltrim($author, '@'); } protected function displayedText() { return $this->text; } } class Link extends NewsFeedItem { private $url; public function __construct($url, $author) { $this->url = $url; $this->author = '@' . ltrim($author, '@'); } protected function displayedText() { return "<a href=\"$this->url\">$this->url</a>"; } }
Мы определяем конструктор, который на данный момент переопределен и невидим:
abstract class NewsFeedItem { /** * @var string references the author's Twitter username */ protected $author; public function __construct() { } /** * @return string an HTML printable version */ public function __toString() { return $this->displayedText() . " -- $this->author"; } /** * @return string */ protected abstract function displayedText(); }
Мы перемещаем общий код внутри конструктора суперкласса. Тесты сейчас не проходят:
abstract class NewsFeedItem { /** * @var string references the author's Twitter username */ protected $author; public function __construct($author) { $this->author = '@' . ltrim($author, '@'); } /** * @return string an HTML printable version */ public function __toString() { return $this->displayedText() . " -- $this->author"; } /** * @return string */ protected abstract function displayedText(); } class Post extends NewsFeedItem { private $text; public function __construct($text, $author) { $this->text = $text; } protected function displayedText() { return $this->text; } } class Link extends NewsFeedItem { private $url; public function __construct($url, $author) { $this->url = $url; } protected function displayedText() { return "<a href=\"$this->url\">$this->url</a>"; } }
Чтобы снова выполнить тест, нам нужно вызвать родительский конструктор:
class Post extends NewsFeedItem { private $text; public function __construct($text, $author) { parent::__construct($author); $this->text = $text; } protected function displayedText() { return $this->text; } } class Link extends NewsFeedItem { private $url; public function __construct($url, $author) { parent::__construct($author); $this->url = $url; } protected function displayedText() { return "<a href=\"$this->url\">$this->url</a>"; } }
Рефакторинг устранил любой источник дублирования в этом классе. Возможны дальнейшие действия по этому рефакторингу:
- родительский конструктор может стать защищенным, поскольку класс является абстрактным и не может быть создан в любом случае.
- Параметры конструкторов подклассов могут быть переупорядочены, чтобы соответствовать суперклассу, ставя $ author на первое место. Но для этого потребуется изменить клиентский код, и это выходит за рамки этого рефакторинга.