После 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 на первое место. Но для этого потребуется изменить клиентский код, и это выходит за рамки этого рефакторинга.