Предположим, у вас есть класс 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();
}