Эта статья о повторяющемся шаблоне, который вы можете применить, встречая цепочку условных выражений или операторов switch.
Хотя их использование может быть ограничено из-за полиморфизма, коммутаторы всегда будут появляться на фабриках или на границах системы, где у вас нет объектов в качестве входных данных, а есть только структура данных, поступающая извне (HTTP-запрос, строка базы данных или файл для чтения). Полиморфизм, по крайней мере, устраняет дублирование этих управляющих структур и оставляет их в одном четком месте.
Однако наличие простых операторов переключения является проблемой: с помощью этого рефакторинга я предлагаю заменить переключатели, которые не могут быть смоделированы с помощью полиморфизма, подходом карты.
Например, мы не можем преобразовать:
switch ($_GET['code']) { case 'A': $this->something(); break; case 'B': $this->somethingElse(); break; }
в
$this->chosenController->action(); // $this->chosenController is an instance of A or B
поскольку $ _GET [‘code’] исходит из самого HTTP-запроса: если мы уберем коммутатор в Factory, он все равно будет там присутствовать, чтобы выбрать класс для реализации для $ this-> selectedController.
Теория
Есть несколько преимуществ для переключения исключения операторов, даже если мы не можем смоделировать каждую ветвь с различной реализацией определенного объекта:
- мы отделяем конфигурацию от логики : список контроллеров здесь охватывает A, B и многие другие классы. Мы проясняем, что является частью повторно используемого объекта (мы могли бы назвать его Маршрутизатором) и что является специфическим для приложения.
- Как следствие, когда мы добавляем или удаляем случаи, мы больше не затрагиваем проверенный код . Мы просто модифицируем конфигурацию.
- Следствием этого является то, что в покрытии кода нет ложных негативов . Во время тестирования переключателей невозможно охватить все ветви, поэтому мы застряли бы с охватом только нескольких из них и добавлением повторных тестов для других, если мы хотим получить высокий охват.
Проблема рефакторинга от перехода к карте состоит в том, что код будет изначально более трудным для чтения. Но это только из-за небольшой кривой обучения.
механика
Предположим, у нас есть этот код (для простоты я опускаю ветку по умолчанию)
switch ($_GET['code']) { case 'A': $this->something(); break; case 'B': $this->somethingElse(); break; }
Рефакторинг переключения на карту означает создание ассоциативного массива (также называемого картой или словарем, в зависимости от вашего языка), где ключи — это функции входных параметров, а значения — это обратные вызовы или объекты, представляющие действие, которое необходимо выполнить. В некоторых простых случаях (преобразованиях) значения являются просто скалярами или объектами.
Оригинальный переключатель становится:
$self = $this; $actions = array( 'A' => function() use($self) { $self->something(); }, 'B' => function() use($self) { $self->somethingElse(); } ); $action = $actions[$_GET['code']]; $action();
Если ветка по умолчанию присутствует, вам придется проверить с isset ($ actions [$ _ GET [‘code’]]) перед вызовом действия. Также обратите внимание, что в PHP 5.4 вы можете напрямую вызывать $ this-> something () внутри замыкания.
Если все методы похожи, вы можете даже упростить карту:
$actions = array( 'A' => 'something', 'B' => 'somethingElse' ); $action = $actions[$_GET['code']]; $this->$action();
в то время как если вам нужно просто выбрать значение или объект еще проще:
$actions = array( 'A' => $something, 'B' => $somethingElse ); return $actions[$_GET['code']];
Код обнаружения ошибок по модулю.
Дальнейший рефакторинг
Чем проще карта конфигурации, тем легче ее извлечь и вставить в конструктор объекта.
Внедрение зависимостей позволяет избежать жесткого кодирования этой конфигурации и позволяет вам тестировать класс без реальной конфигурации: мы можем легко тестировать и разрабатывать маршрутизатор с нуля, не вызывая действий из реальных приложений, а только с помощью некоторых проверок.
Обратите внимание, что чем сложнее конфигурация, тем больше проверок необходимо выполнить во время инъекции; причина в том, что легко записать несуществующий класс или имя метода. Особенно в динамических языках карта защищает вас от некоторых из следующих ошибок:
$actions = array( 'A' => new NotExistentClass(...), ... );
потому что один тест дыма, который покрывает эти линии, покажет виновника. Ветвь переключателя, содержащая новый NotExistentClass () в одной из его ветвей, будет генерировать ошибку только в том случае, если вам повезет выполнить эту ветку (а для полного охвата это означает все ветви).
Вы должны самостоятельно выполнить более сложные проверки работоспособности для конфигурации:
foreach ($actions as $code => $a) { if (!is_callable($a)) { throw new Exception("The action for `$code` is not a callable object or closure. Check the configuration."); } }
Выводы
Этот рефакторинг может стать экземпляром Условных преобразований в регистрации (см. Книгу «Объектно-ориентированные шаблоны реинжиниринга»), которая строит карту динамически, а не требует настройки. В этой версии несколько объектов создают совершенно невидимую конфигурацию, регистрируя себя в промежуточном объекте, таком как Реестр.
Я утверждаю, что центральной конфигурации достаточно для многих вариантов использования, пока у вас не будет очень большого или динамического числа действий или ветвей. Это также меньший шаг при использовании устаревшего кода.
Таким образом, шаблон — это не способ заменить полиморфизм, а всего лишь меньший рефакторинг для применения к самым отвратительным переключателям во время создания экземпляров или на границах, где объекты должны создаваться из сообщений и внешних структур данных.