Статьи

Преобразуйте переключатели в карты

Эта статья о повторяющемся шаблоне, который вы можете применить, встречая цепочку условных выражений или операторов 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.");
    }
}

Выводы

Этот рефакторинг может стать экземпляром Условных преобразований в регистрации (см. Книгу «Объектно-ориентированные шаблоны реинжиниринга»), которая строит карту динамически, а не требует настройки. В этой версии несколько объектов создают совершенно невидимую конфигурацию, регистрируя себя в промежуточном объекте, таком как Реестр.

Я утверждаю, что центральной конфигурации достаточно для многих вариантов использования, пока у вас не будет очень большого или динамического числа действий или ветвей. Это также меньший шаг при использовании устаревшего кода.

Таким образом, шаблон — это не способ заменить полиморфизм, а всего лишь меньший рефакторинг для применения к самым отвратительным переключателям во время создания экземпляров или на границах, где объекты должны создаваться из сообщений и внешних структур данных.