Также известен как Replace Type Code with Subclasses

Рефакторинг Замена кодирования типа подклассами

Что такое кодирование типа? Это когда вместо отдельного типа данных вы имеете набор чисел или строк, который составляет список допустимых значений для какой-то сущности. Зачастую этим конкретным числам и строкам даются понятные имена с помощью констант, что и является причиной их широкого распространения.

Проблема

У вас есть закодированный тип, который непосредственно влияет на поведение программы (основываясь на значениях этого поля, в условных операторах выполняется различный код).

Решение

Для каждого значения закодированного типа, создайте подклассы. А затем, вынесите соответствующие поведения из исходного класса в эти подклассы. Управляющий код замените полиморфизмом.
До
Replace Type Code with Subclasses - Before
После
Replace Type Code with Subclasses - After

Причины рефакторинга

Данный рефакторинг является более сложным случаем замены кодирования типа классом.

Как и в первом рефакторинге, у вас есть какой-то набор простых значений, которые составляют все доступные значения для какого-то поля. Хотя эти значения зачастую заданы как константы и имеют понятные имена, их использование чревато появлением ошибок проверки типов, так как, в сущности, они всё-равно являются значениями примитивных типов. Например, у вас есть метод, принимающий в параметрах одно из таких значений. В определённый момент, вместо константы USER_TYPE_ADMIN со значением "ADMIN", в метод придёт та же строка, но в нижнем регистре "admin", что приведёт к выполнению чего-то другого, нежели планируемое автором поведение.

В данном рефакторинге мы имеем дело с управляющим кодом, таким как условные операторы if, switch или ?:. Другими словами, внутри условий этих операторов используются поля с закодированным значением (напр. $user->type === self::USER_TYPE_ADMIN). Если бы мы применяли здесь замену кодирования типа классом, то все эти управляющие конструкции следовало бы тоже перенести в класс, отвечающих за тип данных. Это, в конечном итоге, сделало бы класс типа очень похожим на исходный класс, и содержащим те же проблемы.

Достоинства

  • Удаление управляющего кода. Вместо большого switch в исходном классе, вы переносите код в соответствующие подклассы. Это улучшает разделение ответственности между классами и упрощает читабельность программы в целом.

  • Если вам потребуется добавить новое значение закодированного типа, все что нужно будет сделать — это добавить новый подкласс, не трогая существующий код (принцип открытости/закрытости).

  • Заменив кодирование типа классами, мы обеспечим возможность контроля и проверки типов значений (type hinting), передаваемых в методы и поля на уровне языка программирования. Чего не сделаешь при помощи простых численных или строковых значений, содержащихся в закодированном типе.

Когда нельзя применить

  • Этот рефакторинг невозможно применить, если у вас уже есть какая-то иерархия классов. Вы не можете создать двойную иерархию при помощи наследования в ООП. Тем не менее, замену кодирования типа можно осуществить, используя композицию вместо наследования. Для этого используйте замену кодирования типа состоянием/стратегией.

  • Если значения закодированного поля может поменяться после того, как объект был создан. При этом нам бы пришлось как-то заменить класс самого объекта на лету, а это не возможно. Тем не менее, альтернативой и здесь является применение замены кодирования типа состоянием/стратегией.

Порядок рефакторинга

  1. Используйте «самоинкапсуляцию поля» (Self encapsulate field) для создания геттера для поля, которое содержит кодирование типа.

  2. Сделайте конструктор суперкласса приватным. Создайте статический фабричный метод с теми же параметрами, что и конструктор суперкласса. Он обязательно должен содержать параметр, который будет принимать стартовые значения закодированного типа. В зависимости от этого параметра, фабричный метод будет создавать объекты различных подклассов. Для этого в его коде придётся создать большой условный оператор, но, по крайней мере, он будет единственным, который действительно необходим, во всем остальном, в дальнейшем, могут позаботиться подклассы и полиморфизм.

  1. Для каждого значения кодированного типа, создайте свой подкласс. В нем переопределите геттер закодированного поля так, чтобы он возвращал соответствующее значение закодированного типа.

  2. Удалите поле с закодированным типом из суперкласса, его геттер сделайте абстрактным.

  3. Теперь, когда у вас появились подклассы, можете начинать перемещать поля и методы из суперкласса в соответствующие подклассы (при помощи спуска поля и спуска метода).

  4. Когда все что можно перемещено, используйте замену условных операторов полиморфизмом, чтобы окончательно избавиться от условных операторов, использующий закодированный тип.

Устали читать?

Сбегайте за подушкой, у нас тут контента на 7 часов чтения.

Или попробуйте наш новый интерактивный курс по рефакторингу. Он более информативный и гораздо более интересный, чем банальный текст.

Узнать больше...

Живой пример

Первый раз здесь? Ничего страшного!

У нас здесь всё просто – интерактивный пример очень похож на видео (но выглядит гораздо круче).

  1. После начала проигрывания, вам показываются разнообразные подсказки и сообщения. Вы продвигаетесь дальше, кликая на них.
  2. Вы можете перематывать шаги, используя стрелки слева.
  3. Кроме того, вы можете посмотреть разницу между первоначальным и получившимся кодом, нажав кнопку с глазом ().
  4. Кнопка компиляции () позвоялет проверить текущий код на наличие ошибок.