В сегодняшнем сценарии мы извлекаем некоторые поля или вычисленные значения из объекта, а затем вызываем метод где-то еще, передавая их в качестве параметров.
Проверяемый код имеет зависимости как от объекта, вызываемого метода, так и от внутренних компонентов, которые он должен извлечь. Альтернатива, к которой ведет нас этот рефакторинг, — передать весь объект целиком.
Почему один объект в качестве параметра?
Прежде всего, передача объекта часто приводит к более четкой сигнатуре метода, так как он принимает сущность более высокого уровня относительно некоторых рассеянных полей. Это также соответствует более короткому списку параметров.
Рефакторинг кода также демонстрирует зависть к функциям и отсутствие инкапсуляции по отношению ко всему объекту: лучше представить это в одном месте (называемый метод), чем переписывать его для каждого вызова (с большим количеством дублирования).
Фаулер отмечает, что рефакторинг закрывается против определенного изменения : случая, когда необходимы новые данные от объекта. В этом случае вам не нужно будет изменять все вызовы, только внутренний код метода и интерфейс объекта.
Примечание о зависимостях
Первоначально существует зависимость от переработанного кода от частей объекта для извлечения и передачи. После рефакторинга эта зависимость устраняется и заменяется зависимостью от рефакторированного метода ко всему объекту. Выберите лучший вариант для вашей ситуации.
Если вам нужно установить некрасивую зависимость, представьте также интерфейс между новым методом и целым объектом. Это делает чудеса для простоты тестирования и повторного использования кода.
альтернативы
У нас есть альтернативы, когда метод извлекает много вещей из объекта: это случай зависти к объектам, и часть его логики должна быть перемещена на сам объект, чтобы сохранить инкапсуляцию (и избежать экспонирования каждого отдельного частного поля с помощью геттера).
Существует также случай, когда весь объект вызывает метод, передавая себя сам. Лично мне не нравится передавать $ this вместо $ this-> field {1,2,3}, когда метод в A вызывается объектом B, передавая свои собственные значения.
Но это вопрос зависимости: вопрос, на который нужно отвечать в каждом конкретном случае, состоит в том, лучше ли передать методу A подмножество интерфейса B (вызов одного или двух методов с хорошими именами) или набор скалярных параметров (полей / коллабораторов B).
меры
- Добавьте в метод новый параметр : весь объект. Используйте Добавить параметр.
- Определите параметры, которые нужно взять с объекта.
- Для каждого из них замените внутреннюю ссылку на код, который получает ее от всего объекта .
- Удалите параметр и перезапустите следующий. Используйте параметр «Удалить параметр», чтобы избежать взлома кода.
Не забудьте упростить код, окружающий вызовы, теперь, когда они передают весь объект: извлечение параметров, которые больше не передаются, не требуется.
пример
Мы начинаем с метода, принимающего некоторую информацию о дате: это временной интервал для календаря, который указывается для определенного месяца. Он сообщает клиентскому коду, будет ли в слоте хотя бы одна неделя при запуске в определенный день. Неделя определяется как более семи дней.
Мы хотим передать дату непосредственно вместо ее частей, которые являются скалярными значениями.
<?php class PreserveWholeObject extends PHPUnit_Framework_TestCase { public function testTheSlotEvaluatesItsLength() { $today = new DateTime('2011-11-23'); $slot = new MonthSpecificSlot(); $this->assertTrue($slot->containsAWeek($today->format('m'), $today->format('d'))); } } class MonthSpecificSlot { public function containsAWeek($month, $day) { $reference = new DateTime('2011-' . $month . '-01'); $daysInMonth = $reference->format('t'); return $day + 6 <= $daysInMonth; } }
Мы добавляем новый параметр: объект DateTime, из которого извлекаются день и месяц. Нам не нужно устранять зависимость от объекта путем введения интерфейса, потому что DateTime — это простой ValueObject, представленный языком, как, например, ArrayIterator.
Мы выполняем преобразования для обоих параметров за один шаг, хотя в сложных ситуациях вы можете выполнить их по одному за раз. Имеет смысл объединить параметры вместе, так как вы будете изменять одни и те же файлы, содержащие вызовы для каждого из них.
<?php class PreserveWholeObject extends PHPUnit_Framework_TestCase { public function testTheSlotEvaluatesItsLength() { $today = new DateTime('2011-11-23'); $slot = new MonthSpecificSlot(); $this->assertTrue($slot->containsAWeek($today, $today->format('m'), $today->format('d'))); } } class MonthSpecificSlot { public function containsAWeek($startDate, $month, $day) { $reference = new DateTime('2011-' . $month . '-01'); $daysInMonth = $reference->format('t'); return $day + 6 <= $daysInMonth; } }
Теперь мы извлекаем параметры изнутри объекта в методе. Тест все еще проходит.
class MonthSpecificSlot { public function containsAWeek($startDate, $month, $day) { $month = $startDate->format('m'); $day = $startDate->format('d'); $reference = new DateTime('2011-' . $month . '-01'); $daysInMonth = $reference->format('t'); return $day + 6 <= $daysInMonth; } }
Мы удаляем неиспользуемые параметры, действуя также на клиентском коде.
<?php class PreserveWholeObject extends PHPUnit_Framework_TestCase { public function testTheSlotEvaluatesItsLength() { $today = new DateTime('2011-11-23'); $slot = new MonthSpecificSlot(); $this->assertTrue($slot->containsAWeek($today)); } } class MonthSpecificSlot { public function containsAWeek($startDate) { $month = $startDate->format('m'); $day = $startDate->format('d'); $reference = new DateTime('2011-' . $month . '-01'); $daysInMonth = $reference->format('t'); return $day + 6 <= $daysInMonth; } }
Рефакторинг завершен, но мы можем сделать еще одно уточнение: получить количество дней в месяце из исходного объекта DateTime. Рефакторинг Mikado, такой как этот метод, позволяет еще больше упростить код.
class MonthSpecificSlot { public function containsAWeek($startDate) { $day = $startDate->format('d'); $daysInMonth = $startDate->format('t'); return $day + 6 <= $daysInMonth; } }