Рефакторинг сегодня разбивает метод на несколько: он разбивает метод на части в соответствии с кодом, который выполняется в ответ на один из параметров. Этот параметр может принимать только несколько значений или несколько эквивалентных классов значений; каждый выбор приводит к новой версии метода.
Как правило, параметр является логическим , и два результата могут быть предоставлены согласно его значению. Также Value Objects, обертывающие целые числа и фиксированные строки, попадают в одну категорию (конечный набор значений)
Этот рефакторинг является обратным к параметризации метода , который принимает несколько версий метода и объединяет их, добавляя отличительный параметр к вызовам.
Зачем делить метод на несколько версий?
Поскольку по предположению существует только несколько дискретных значений для выбранного параметра, можно легко сопоставить несколько различных методов с исходным. Этот факт включает рефакторинг, но не говорит нам, когда это правильное направление для подражания.
Преимущества нескольких методов очевидны, когда в исходном методе найдены switch () или if () для параметра . Вы можете избежать условного поведения, которое выполняет сильно отличающийся код в зависимости от значения параметра ключа.
Интерфейс объекта становится также ясным , как у вас есть набор открытых методов , которые можно назвать хорошо, а не догоняющего всякого имени , как процесс ($ ActionType, …).
Наконец, этот рефакторинг сокращает список параметров (удаляя один из аргументов) и тело метода, выбирая только одну ветвь условных операторов для каждого нового метода.
Когда его не использовать?
Фаулер указывает в своей книге « Рефакторинг», что вам не следует вводить явные методы, когда вы думаете, что параметры изменятся новыми, неожиданными способами . Несколько реализаций объекта параметра легко добавить, в то время как несколько версий метода могут нарушить API исходного объекта и вынудить grep весь клиентский код отрегулировать вызовы.
В любом случае этот рефакторинг учитывает изменчивость метода и обрабатывает его в объекте, где он находится . Перемещение этой изменчивости из объекта было бы жизнеспособным с полиморфизмом, и это предпочтительно, когда вам нужно сохранить растяжимость. Но если вам не нужна расширяемость, а всего лишь несколько разных вариантов использования, этот рефакторинг заставит вас выразить их наилучшим образом: по одному методу для каждого.
меры
- Создайте явные методы для каждого из путей кода, на которые вы хотите разбить метод. Например, если у вас есть переключатель с 3 случаями, вы создадите 3 явных метода.
- Делегируйте эти методы изнутри старого монолитного. Этот шаг сокращает исходный метод и гарантирует, что вы ничего не забудете.
- Измените вызовы кода клиента, чтобы использовать новые методы.
- Удалите оригинальный метод, когда закончите.
Тесты должны проходить между каждым шагом: нет причин нарушать код при применении этого рефакторинга.
пример
Пример основан на контексте форума: потоки являются объектами домена и не связаны с многопоточностью.
В объекте Thread у нас есть установщик для логического параметра. Также есть добытчик. Открытое поле имеет значение false или true в зависимости от состояния потока.
Сеттер обновляет вычисляемое поле по-разному, основываясь на булевом параметре: мы хотим устранить условное условие и предоставить клиентскому коду более точный API-интерфейс.
<?php class ReplaceParameterWithExplicitMethods extends PHPUnit_Framework_TestCase { public function testThreadCanBeClosed() { $thread = new Thread('Ubuntu on EEE Pc'); $thread->setOpen(false); $this->assertFalse($thread->getOpen()); $this->assertEquals('[closed] Ubuntu on EEE Pc', $thread->__toString()); } public function testThreadCanBeOpened() { $thread = new Thread('Ubuntu on EEE Pc'); $thread->setOpen(true); $this->assertTrue($thread->getOpen()); $this->assertEquals('Ubuntu on EEE Pc', $thread->__toString()); } } class Thread { private $title; private $open; public function __construct($title) { $this->title = $title; } public function setOpen($state) { $this->open = $state; if (!$this->open) { $this->label = '[closed] ' . $this->title; } else { $this->label = $this->title; } } public function getOpen() { return $this->open; } public function __toString() { return $this->label; } }
Мы преобразуем setter: close () и open () — лучшие имена для методов, которые делают больше, чем простое присваивание полю. Геттер не содержит разных ветвей исполнения.
class Thread { private $title; private $open; public function __construct($title) { $this->title = $title; } public function setOpen($state) { $this->open = $state; if (!$this->open) { $this->label = '[closed] ' . $this->title; } else { $this->label = $this->title; } } public function open() { $this->open = true; $this->label = $this->title; } public function close() { $this->open = false; $this->label = '[closed] ' . $this->title; } public function getOpen() { return $this->open; } public function __toString() { return $this->label; } }
Мы называем новые методы изнутри условным. Старый метод действительно некрасиво видеть сейчас, но он исчезнет через мгновение.
class Thread { private $title; private $open; public function __construct($title) { $this->title = $title; } public function setOpen($open) { if (!$open) { $this->close(); } else { $this->open(); } } public function open() { $this->open = true; $this->label = $this->title; } public function close() { $this->open = false; $this->label = '[closed] ' . $this->title; } public function getOpen() { return $this->open; } public function __toString() { return $this->label; } }
Тесты все еще проходят.
[giorgio@Desmond:practical-php-refactoring]$ phpunit ReplaceParameterWithExplicitMethods.php PHPUnit 3.5.15 by Sebastian Bergmann. .. Time: 0 seconds, Memory: 3.50Mb OK (2 tests, 4 assertions)
Теперь мы можем изменить вызовы из клиентского кода (только тесты в этом примере).
<?php class ReplaceParameterWithExplicitMethods extends PHPUnit_Framework_TestCase { public function testThreadCanBeClosed() { $thread = new Thread('Ubuntu on EEE Pc'); $thread->close(); $this->assertFalse($thread->getOpen()); $this->assertEquals('[closed] Ubuntu on EEE Pc', $thread->__toString()); } public function testThreadCanBeOpened() { $thread = new Thread('Ubuntu on EEE Pc'); $thread->open(); $this->assertTrue($thread->getOpen()); $this->assertEquals('Ubuntu on EEE Pc', $thread->__toString()); } }
Наконец, мы можем удалить старую версию метода, которая больше не достигается ни одним вызовом.
class Thread { private $title; private $open; public function __construct($title) { $this->title = $title; } public function open() { $this->open = true; $this->label = $this->title; } public function close() { $this->open = false; $this->label = '[closed] ' . $this->title; } public function getOpen() { return $this->open; } public function __toString() { return $this->label; } }