Autumn SALE

Извлечение подкласса

Также известен как: Extract Subclass

Проблема

Класс имеет фичи, которые используются только в определённых случаях.

Решение

Создайте подкласс и используйте его в этих случаях.

До
Extract Subclass - Before
После
Extract Subclass - After

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

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

Достоинства

  • Создать подкласс довольно легко и быстро.

  • Можно выделить несколько разных подклассов, если основной класс реализует несколько подобных особых случаев.

Недостатки

  • Несмотря на всю очевидную простоту, Наследование может завести вас в тупик, если придётся выделить несколько различных иерархий классов. Например, если у вас был класс Собаки, поведение которого отличается в зависимости от размера и длины шерсти собак. Вы выделили из него две иерархии:

    • по размеру: Большие, Средние и Маленькие

    • по длине шерсти: Гладкошёрстные и Длинношёрстные

  • И все бы хорошо, но у вас начнутся проблемы, когда нужно будет создать одновременно Большую и Гладкошёрстную собаку, так как объект можно создать лишь из одного класса. С другой стороны, эту проблему можно обойти, используя Композицию вместо Наследования (см. паттерн Стратегия). Другими словами, класс Собака будет иметь два поля-компонента — размер и длина шерсти. В эти поля вы будете подставлять объекты-компоненты необходимых классов. Например, вы сможете создать Собаку, имеющую БольшойРазмер и ДлиннуюШерсть.

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

  1. Создайте новый подкласс из интересующего вас класса.

  2. Если для создания объектов из подкласса будут нужны какие-то дополнительные данные, создайте конструктор и дополните его нужными параметрами. Не забудьте вызвать родительскую реализацию конструктора.

  3. Найдите все вызовы конструктора родительского класса. В тех случаях, когда требуется функциональность подкласса, замените родительский конструктор конструктором подкласса.

  4. Переместите нужные методы и поля из родительского класса в подкласс. Используйте для этого спуск метода и спуск поля. Проще всего начинать перенос с методов. Так поля будут доступны для них все время: из родительского класса до переноса, и из самого подкласса после окончания переноса.

  5. После того как подкласс готов, найдите все старые поля, которые управляли тем, какой набор функций должен выполняться. Эти поля можно удалить, заменив полиморфизмом все условные операторы, в которых они использовались. Простой пример — у вас в классе Автомобиль было поле isElectricCar, и в зависимости от него, в методе refuel() в машину либо заливается бензин, либо заряжается электричество. В результате рефакторинга, поле isElectricCar будет удалено, а классы Автомобиль и ЭлектроАвтомобиль будут иметь свои реализации метода refuel().