В сегодняшнем сценарии есть два или более не связанных между собой класса с похожими членами, такими как общие методы или поля. Хотя эти классы могут быть уже связаны на семантическом уровне (посредством дублированных имен или пространств имен), ничто не связывает их в уме интерпретатора 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>"; } }