Также известен как Replace Method with Method Object

Рефакторинг Замена метода объектом методов

Проблема

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

Решение

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

До
class Order {
  //...
  public double price() {
    double primaryBasePrice;
    double secondaryBasePrice;
    double tertiaryBasePrice;
    // long computation.
    //...
  }
}
После
class Order {
  //...
  public double price() {
    return new PriceCalculator(this).compute();
  }
}

class PriceCalculator {
  private double primaryBasePrice;
  private double secondaryBasePrice;
  private double tertiaryBasePrice;
  
  public PriceCalculator(Order order) {
    // copy relevant information from order object.
    //...
  }
  
  public double compute() {
    // long computation.
    //...
  }
}
До
public class Order 
{
  //...
  public double Price() 
  {
    double primaryBasePrice;
    double secondaryBasePrice;
    double tertiaryBasePrice;
    // long computation.
    //...
  }
}
После
public class Order 
{
  //...
  public double Price() 
  {
    return new PriceCalculator(this).Compute();
  }
}

public class PriceCalculator 
{
  private double primaryBasePrice;
  private double secondaryBasePrice;
  private double tertiaryBasePrice;
  
  public PriceCalculator(Order order) 
  {
    // copy relevant information from order object.
    //...
  }
  
  public double Compute() 
  {
    // long computation.
    //...
  }
}
До
class Order {
  ...
  public function price() {
    $primaryBasePrice = 10;
    $secondaryBasePrice = 20;
    $tertiaryBasePrice = 30;
    // long computation.
    ...
  }
}
После
class Order {
  ...
  public function price() {
    return new PriceCalculator($this)->compute();
  }
}

class PriceCalculator {
  private $primaryBasePrice;
  private $secondaryBasePrice;
  private $tertiaryBasePrice;
  
  public __construct(Order $order) {
    // copy relevant information from order object.
    ...
  }
  
  public function compute() {
    // long computation.
    ...
  }
}
До
class Order:
    #...
    def price(self):
        primaryBasePrice = 0
        secondaryBasePrice = 0
        tertiaryBasePrice = 0
        # long computation.
        #...
После
class Order:
    #...
    def price(self):
        return PriceCalculator(self).compute()


class PriceCalculator:
    def __init__(self, order):
        self._primaryBasePrice = 0
        self._secondaryBasePrice = 0
        self._tertiaryBasePrice = 0
        # copy relevant information from order object.
        #...

    def compute(self):
        # long computation.
        #...

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

Метод слишком длинный, и вы не можете его разделить из-за хитросплетения локальных переменных, которые сложно изолировать друг от друга.

Первым шагом к решению проблемы будет выделение всего этого метода в отдельный класс и превращение его локальных переменных в поля.

Во-первых, это позволит вам изолировать проблему в пределах этого класса, а во-вторых, расчистит дорогу для разделения большого метода на методы поменьше, которые, к тому же, не подходили бы к смыслу оригинального класса.

Достоинства

  • Изоляция длинного метода в собственном классе позволяет остановить бесконтрольный рост метода. Кроме того, даёт возможность разделить его на подметоды в рамках своего класса, не засоряя служебными методами оригинальный класс.

Недостатки

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

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

  1. Создайте новый класс. Дайте ему название, основываясь на предназначении метода, который рефакторите.
  2. В новом классе создайте приватное поле для хранения ссылки на экземпляр класса, в котором раньше находился метод. Эту ссылку потом можно будет использовать, чтобы брать из оригинального объекта нужные данные, если потребуется.
  3. Создайте отдельное приватное поле для каждой локальной переменной метода.
  4. Создайте конструктор, который принимает в параметрах значения всех локальных переменных метода, а также инициализирует соответствующие приватные поля.
  5. Объявите основной метод и скопируйте в него код оригинального метода, заменив локальные переменные приватным полями.
  6. Замените тело оригинального метода в исходном классе созданием объекта-метода и вызовом его основного метода.

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

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

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

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