Summer SALE

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

Також відомий як: 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 { // ... public 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 { public function getSpeed() { return $this->getBaseSpeed(); } } class African extends Bird { public function getSpeed() { return $this->getBaseSpeed() - $this->getLoadFactor() * $this->numberOfCoconuts; } } class NorwegianBlue extends Bird { public 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 self.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(self): return 0 if self.isNailed else self.getBaseSpeed(self.voltage) # Somewhere in client code speed = bird.getSpeed()
До
class Bird { // ... getSpeed(): number { switch (type) { case EUROPEAN: return getBaseSpeed(); case AFRICAN: return getBaseSpeed() - getLoadFactor() * numberOfCoconuts; case NORWEGIAN_BLUE: return (isNailed) ? 0 : getBaseSpeed(voltage); } throw new Error("Should be unreachable"); } }
Після
abstract class Bird { // ... abstract getSpeed(): number; } class European extends Bird { getSpeed(): number { return getBaseSpeed(); } } class African extends Bird { getSpeed(): number { return getBaseSpeed() - getLoadFactor() * numberOfCoconuts; } } class NorwegianBlue extends Bird { getSpeed(): number { return (isNailed) ? 0 : getBaseSpeed(voltage); } } // Somewhere in client code let speed = bird.getSpeed();

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

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

  • класу об’єкта або інтерфейсу, який він реалізує;

  • значення якогось з полів об’єкта;

  • результату виклику одного з методів об’єкта.

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

Переваги

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

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

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

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

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

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

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

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

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

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

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

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

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

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