Предположим, у вас есть класс God, который полон операторов if и case или логических флагов. Многие рефакторинги пытаются разделить обязанности на более мелкие объекты: методы, подклассы или соавторы.
Один из вариантов начать разбивать класс на основе наследования. Конечным результатом является множество маленьких подклассов, каждый из которых моделирует особый случай, который был погружен в исходный класс-гигант.
Extract Иерархия рефакторинг идет от одного сложного класса один абстрактного базового класса с крючками для подклассов для размещения специального поведения. Это очень масштабный рефакторинг, и предполагается, что вы можете изменять экземпляры исходного класса и моделировать особые случаи в состоянии самих экземпляров.
Большая часть времени для реализации рефакторинга уходит на изучение, которые являются правильными случаями для моделирования в качестве подклассов , больше, чем в реальном извлечении. Фаулер предлагает выполнять это небольшими шагами в течение нескольких дней.
Пахнет
Как вы узнаете, что большой класс может быть разбит в особых случаях? Для начала, он содержит несколько сотен строк кода. Кроме того, его методы содержат любой из этих запахов:
- логические поля , используемые для решения, что делать.
- Типы кодов , обобщение булевых полей на O (N) частных случаев.
- Если / еще цепочки или переключатели , особенно в зависимости от состояния текущего объекта.
Обратите внимание, что создание подклассов не всегда лучший выбор для учета вариаций: это лучше, чем отдельный класс, но только тогда, когда вы читаете код; во время выполнения все подклассы копируют в них логику своих предков.
меры
- Определите вариант, который вы хотите извлечь. Это должно быть важным вопросом, поскольку подклассы могут охватывать только одну ось изменчивости .
- Замените конструктор класса на метод фабрики , чтобы централизовать выбор класса для создания экземпляра.
- Создайте подкласс (ы) и перепишите фабричный метод соответственно.
- Извлечение методов, пытающихся отделить код переменной (содержащий условные выражения) от фиксированного. Эти методы изменчивости являются ключом к пониманию того, какие особые случаи моделируются классом.
- Скопируйте методы изменчивости в подклассы : каждая копия затем может быть преобразована в особый случай, рассматриваемый подклассом.
- Объявить оригинальный класс абстрактным .
- Удалите все тела методов изменчивости в суперклассе : если их ответственность была правильно разделена на подклассы, тесты и клиентский код не заметят.
пример
Мы начинаем с помощника представления, представляющего некоторую визуализацию темы форума: его заголовка и, возможно, других комбинаций полей. У нас есть два случая, связанных с одним классом, один нормальный, а другой специальный: тема может быть в качестве доказательства или нет.
<?php class ExtractHierarchy extends PHPUnit_Framework_TestCase { public function testAnOrdinaryTopicDisplaysItsTitleAsNormalText() { $topic = new Topic('OOP in R'); $this->assertEquals('<div class="title">OOP in R</div>', $topic->title()); } public function testATopicInEvidenceDisplaysItselfAsStronger() { $topic = new Topic('OOP in R', true); $this->assertEquals('<div class="title"><strong>OOP in R</strong></div>', $topic->title()); } } class Topic { private $title; private $inEvidence; public function __construct($title, $inEvidence = false) { $this->title = $title; $this->inEvidence = $inEvidence; } public function title() { $title = '<div class="title">'; if ($this->inEvidence) { $title .= "<strong>$this->title</strong>"; } else { $title .= $this->title; } $title .= '</div>'; return $title; } }
Мы инкапсулируем логику создания, пока очень простую, в метод Factory и делаем конструктор закрытым. Мы создаем два подкласса и корректируем логику создания, чтобы больше не создавать экземпляры универсальной темы суперкласса.
<?php class ExtractHierarchy extends PHPUnit_Framework_TestCase { public function testAnOrdinaryTopicDisplaysItsTitleAsNormalText() { $topic = Topic::fromFields('OOP in R'); $this->assertEquals('<div class="title">OOP in R</div>', $topic->title()); } public function testATopicInEvidenceDisplaysItselfAsStronger() { $topic = Topic::fromFields('OOP in R', true); $this->assertEquals('<div class="title"><strong>OOP in R</strong></div>', $topic->title()); } } class Topic { private $title; private $inEvidence; public static function fromFields($title, $inEvidence = false) { if ($inEvidence) { return new InEvidenceTopic($title, $inEvidence); } else { return new OrdinaryTopic($title, $inEvidence); } } private function __construct($title, $inEvidence) { $this->title = $title; $this->inEvidence = $inEvidence; } public function title() { $title = '<div class="title">'; if ($this->inEvidence) { $title .= "<strong>$this->title</strong>"; } else { $title .= $this->title; } $title .= '</div>'; return $title; } } class OrdinaryTopic extends Topic { } class InEvidenceTopic extends Topic { }
Мы извлекаем единственный метод с изменчивостью, связанный с логическим полем, которое мы хотим исключить.
class Topic { private $title; private $inEvidence; public static function fromFields($title, $inEvidence = false) { if ($inEvidence) { return new InEvidenceTopic($title, $inEvidence); } else { return new OrdinaryTopic($title, $inEvidence); } } private function __construct($title, $inEvidence) { $this->title = $title; $this->inEvidence = $inEvidence; } public function title() { $title = '<div class="title">'; $title .= $this->decoratedTitleText(); $title .= '</div>'; return $title; } protected function decoratedTitleText() { if ($this->inEvidence) { return "<strong>$this->title</strong>"; } else { return $this->title; } } }
Мы копируем его в подклассы:
class OrdinaryTopic extends Topic { protected function decoratedTitleText() { if ($this->inEvidence) { return "<strong>$this->title</strong>"; } else { return $this->title; } } } class InEvidenceTopic extends Topic { protected function decoratedTitleText() { if ($this->inEvidence) { return "<strong>$this->title</strong>"; } else { return $this->title; } } }
Мы специализируемся на двух экземплярах, чтобы не использовать поле.
class OrdinaryTopic extends Topic { protected function decoratedTitleText() { return $this->title; } } class InEvidenceTopic extends Topic { protected function decoratedTitleText() { return "<strong>$this->title</strong>"; } }
Теперь мы можем удалить тело исходного метода изменчивости (но не сигнатуру) и объявить реферат базового класса. Дальнейшее упрощение также удаляет поле $ this-> inEvidence.
abstract class Topic { protected $title; public static function fromFields($title, $inEvidence = false) { if ($inEvidence) { return new InEvidenceTopic($title); } else { return new OrdinaryTopic($title); } } private function __construct($title) { $this->title = $title; } public function title() { $title = '<div class="title">'; $title .= $this->decoratedTitleText(); $title .= '</div>'; return $title; } /** * @return string */ abstract protected function decoratedTitleText(); }