Також відомий як Replace Conditional with Polymorphism

Рефакторинг Заміна умовного оператора поліморфізмом

Проблема

У вас є умовний оператор, який, залежно від типу або властивостей об'єкту, виконує різні дії.

Рішення

Створіть підкласи, яким відповідають гілки умовного оператора. У них створіть спільний метод і перемістіть в нього код з відповідної гілки умовного оператора. Згодом проведіть заміну умовного оператора на виклик цього методу. Таким чином, потрібна реалізація вибиратиметься через поліморфізм залежно від класу об'єкту.
До
class Bird {
  //...
  double getSpeed() {
    switch (type) {
      case EUROPEAN:
        return getBaseSpeed();
      case AFRICAN:
        return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
      case NORWEGIAN_BLUE:
        return (isNailed) ? 0 : getBaseSpeed(voltage);
    }
    throw new RuntimeException("Should be unreachable");
  }
}
Після
abstract class Bird {
  //...
  abstract double getSpeed();
}

class European extends Bird {
  double getSpeed() {
    return getBaseSpeed();
  }
}
class African extends Bird {
  double getSpeed() {
    return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
  }
}
class NorwegianBlue extends Bird {
  double getSpeed() {
    return (isNailed) ? 0 : getBaseSpeed(voltage);
  }
}

// Somewhere in client code
speed = bird.getSpeed();
До
public class Bird 
{
  //...
  public double GetSpeed() 
  {
    switch (type) 
    {
      case EUROPEAN:
        return GetBaseSpeed();
      case AFRICAN:
        return GetBaseSpeed() - GetLoadFactor() * numberOfCoconuts;
      case NORWEGIAN_BLUE:
        return isNailed ? 0 : GetBaseSpeed(voltage);
      default:
        throw new Exception("Should be unreachable");
    }
  }
}
Після
public abstract class Bird 
{
  //...
  public abstract double GetSpeed();
}

class European: Bird 
{
  public override double GetSpeed() 
  {
    return GetBaseSpeed();
  }
}
class African: Bird 
{
  public override double GetSpeed() 
  {
    return GetBaseSpeed() - GetLoadFactor() * numberOfCoconuts;
  }
}
class NorwegianBlue: Bird
{
  public override double GetSpeed() 
  {
    return isNailed ? 0 : GetBaseSpeed(voltage);
  }
}

// Somewhere in client code
speed = bird.GetSpeed();
До
class Bird {
  ...
  function getSpeed() {
    switch ($this->type) {
      case EUROPEAN:
        return $this->getBaseSpeed();
      case AFRICAN:
        return $this->getBaseSpeed() - $this->getLoadFactor() * $this->numberOfCoconuts;
      case NORWEGIAN_BLUE:
        return ($this->isNailed) ? 0 : $this->getBaseSpeed($this->voltage);
    }
    throw new Exception("Should be unreachable");
  }
  ...
}
Після
abstract class Bird {
  ...
  abstract function getSpeed();
  ...
}

class European extends Bird {
  function getSpeed() {
    return $this->getBaseSpeed();
  }
}
class African extends Bird {
  function getSpeed() {
    return $this->getBaseSpeed() - $this->getLoadFactor() * $this->numberOfCoconuts;
  }
}
class NorwegianBlue extends Bird {
  function getSpeed() {
    return ($this->isNailed) ? 0 : $this->getBaseSpeed($this->voltage);
  }
}

// Somewhere in Client code.
$speed = $bird->getSpeed();
До
class Bird:
    #...
    def getSpeed(self):
        if self.type == EUROPEAN:
            return self.getBaseSpeed();
        elif self.type == AFRICAN:
            return self.getBaseSpeed() - self.getLoadFactor() * self.numberOfCoconuts;
        elif self.type == NORWEGIAN_BLUE:
            return 0 if isNailed else self.getBaseSpeed(self.voltage)
        else:
            raise Exception("Should be unreachable")
Після
class Bird:
    #...
    def getSpeed(self):
        pass

class European(Bird):

    def getSpeed(self):
        return self.getBaseSpeed()
    
    
class African(Bird):

    def getSpeed(self):
        return self.getBaseSpeed() - self.getLoadFactor() * self.numberOfCoconuts


class NorwegianBlue(Bird):

    def getSpeed():
        return 0 if self.isNailed else self.getBaseSpeed(self.voltage)

# Somewhere in client code
speed = bird.getSpeed()

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

Цей рефакторинг може допомогти, якщо у вас в коді є умовні оператори, які виконують різну роботу, залежно від:

  • класу об'єкту або інтерфейсу, який він реалізує;
  • значення якогось з полів об'єкту;
  • результату виклику одного з методів об'єкту.

При цьому, якщо у вас з'явиться новий тип або властивість об'єкту, треба буде шукати і додавати код в усі схожі умовні оператори. Таким чином, користь від цього рефакторингу збільшується, якщо умовних операторів більш, ніж один, і вони розкидані по усіх методах об'єкту.

Переваги

  • Цей рефакторинг реалізує принцип кажи, а не запитуй: замість того, щоб запитувати об'єкт про його стан, а потім виконувати на підставі цього якісь дії, набагато простіше просто сказати йому, що треба робити, а як це робити він вирішить сам.

  • Вбиває дублювання коду. Ви позбавляєтеся від безлічі майже однакових умовних операторів.

  • Якщо вам потрібно буде додати новий варіант виконання, все, що доведеться зробити, це додати новий підклас, не чіпаючи існуючий код (принцип відкритості/закритості).

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

Підготовка до рефакторингу

Щоб виконати цей рефакторинг, вам слід мати готову ієрархію класів, в яких міститиметься альтернативна поведінка. Якщо такої ієрархії ще немає, треба створити її. У цьому можуть допомогти інші рефакторинги:

  • Заміна кодування типу підкласами. При цьому для усіх значень якоїсь властивості об'єкту будуть створені свої підкласи. Це хоч і простий, але менш гнучкий спосіб, оскільки не можна буде створити підкласи для інших властивостей об'єкту.

  • Заміна кодування типу станом/стратегією. При цьому для певної властивості об'єкту буде виділений свій клас і з нього створені підкласи для кожного значення цієї властивості. Поточний клас утримуватиме посилання на об'єкти такого типу і делегуватиме їм виконання.

Подальші кроки цього рефакторингу вважають, що ви вже створили ієрархію.

Кроки рефакторингу

  1. Якщо умовний оператор знаходиться в методі, який виконує ще якісь дії, «витягніть його в новий метод» (Extract method).

  2. Для кожного підкласу ієрархії потібно перевизначити метод, ща містить умовний оператор, і скопіювати туди код відповідної гілки оператора.

  3. Видаліть цю гілку з умовного оператора.

  4. Повторюйте заміну, поки умовний оператор не спорожніє. Потім видаліть умовний оператор і оголосите метод абстрактним.

Замучились читати?

Збігайте за подушкою, в нас тут контенту приблизно на 7 годин читання.

Або спробуйте наш новий інтерактивний курс з рефакторингу. Він більш інформативний та набагато цікавіший за банальний тест.

Дізнатися більше...

Живий приклад

Перший раз у нас? Нічого страшного!

У нас тут все просто – цей інтерактивний приклад дуже схожий на відео (хоча й виглядає набагато крутіше).

  1. Натискаєте велику кнопку "Почати", а далі слідуєте усім підказкам.
  2. Ви можете перемотувати програвання, використовуючи стрілки зліва.
  3. Окрім того, ви можете подивитися різницю між стартовим та отриманим кодом, натиснувши кнопку ока ().
  4. Кнопка компіляції та тестування () дає змогу перевірити код на наявність помилок.