В сегодняшнем сценарии есть два или более не связанных между собой класса с похожими членами, такими как общие методы или поля. Хотя эти классы могут быть уже связаны на семантическом уровне (посредством дублированных имен или пространств имен), ничто не связывает их в уме интерпретатора PHP (если он есть).
Extract суперкласса рефакторинга устанавливает иерархию наследования путем введения базового класса , что дублированные классы могут продлить. Впоследствии, члены класса могут быть перемещены в суперкласс, чтобы быть объединенными и сохраненными запертыми в одном месте.
Почему базовый класс?
Суперкласс — это один из способов, который мы можем использовать для устранения дублирования, в данном случае с использованием наследования. Часто более чистой альтернативой является делегирование, которое мы увидим позже в этой серии. Этот рефакторинг является активатором для рефакторингов Pull Up, которые могут быть выполнены только после создания иерархии.
Извлеченный суперкласс является абстрактным: обычно нет причин делать его конкретным.
Тесты также не являются проблемой, так как обычно они уже охватывают два класса, где возникло дублирование; поэтому нет необходимости устанавливать независимый тест для суперкласса. Угловой случай — это когда у вас есть много подклассов, которые содержат мало логики и просто конфигурации: в этом случае было бы существенной экономией добавить конкретный класс только для тестирования. Окончательный результат будет тестовым примером для этого фиктивного, конкретного класса; тесты для N классов производства бетона будут удалены.
меры
- Создайте абстрактный суперкласс . Выберите его имя в качестве минимального общего знаменателя для существующих классов.
- Исполнить Pull Up Field , Pull Up Method , и Pull Up Constructory тело , где это применимо. Порядок применения имеет значение: поля могут быть перемещены в первую очередь, так как методы будут продолжать видеть их в подклассах.
- После каждой (или пары, если вы храбрых) операций по извлечению запускайте тесты .
Предложение Фаулера заключается в том, что если между методами подклассов все еще есть общие части, вы можете применить метод извлечения и метод подтягивания, чтобы устранить все формы дублирования.
Приятным побочным эффектом этого рефакторинга является то, что если клиентский код зависит только от 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>";
}
}