Также известен как Introduce Null Object

Рефакторинг Введение Null-объекта

Проблема

Из-за того, что некоторые методы возвращают `null` вместо реальных объектов, у вас в коде присутствует множество проверок на `null`.

Решение

Вместо `null` возвращайте Null-объект, который предоставляет поведение по умолчанию.
До
if (customer == null) {
  plan = BillingPlan.basic();
}
else {
  plan = customer.getPlan();
}
После
class NullCustomer extends Customer {
  boolean isNull() {
    return true;
  }
  Plan getPlan() {
    return new NullPlan();
  }
  // Some other NULL functionality.
}

// Replace null values with Null-object.
customer = (order.customer != null) ?
  order.customer : new NullCustomer();

// Use Null-object as if it's normal subclass.
plan = customer.getPlan();
До
if (customer == null) 
{
  plan = BillingPlan.Basic();
}
else 
{
  plan = customer.GetPlan();
}
После
public sealed class NullCustomer: Customer 
{
  public override bool IsNull 
  {
    get { return true; }
  }
  
  public override Plan GetPlan() 
  {
    return new NullPlan();
  }
  // Some other NULL functionality.
}

// Replace null values with Null-object.
customer = order.customer ?? new NullCustomer();

// Use Null-object as if it's normal subclass.
plan = customer.GetPlan();
До
if ($customer == null)
  $plan = BillingPlan::basic();
else
  $plan = $customer->getPlan();
После
class NullCustomer extends Customer {
  function isNull() {
    return true;
  }
  function getPlan() {
    return new NullPlan();
  }
  // Some other NULL functionality.
}

// Replace null values with Null-object.
$customer = ($order->customer != null) ?
  $order->customer :
  new NullCustomer();

// Use Null-object as if it's normal subclass.
$plan = $customer->getPlan();
До
if customer == None:
    plan = BillingPlan.basic()
else:
    plan = customer.getPlan()
После
class NullCustomer(Customer):

    def isNull(self):
        return True
    
    def getPlan(self):
        return self.NullPlan()
        
    # Some other NULL functionality.

# Replace null values with Null-object.
customer = order.customer if order.customer != None else NullCustomer()

# Use Null-object as if it's normal subclass.
plan = customer.getPlan()

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

Десятки проверок на null усложняют и засоряют код.

Недостатки

  • За отказ от условных операторов вы платите ещё одним новым классом.

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

  1. Из интересующего вас класса создайте подкласс, который будет выполнять роль Null-объекта.

  2. В обоих классах создайте метод isNull(), который будет возвращать true для Null-объекта и false для реального класса.

  3. Найдите все места, где код может вернуть null вместо реального объекта. Измените этот код так, чтобы он возвращал Null-объект.

  4. Найдите все места, где переменные реального класса сравниваются с null. Замените такие проверки вызовом метода isNull().

    • Если в этих условных операторах при значении не равном null выполняются методы исходного класса, переопределите эти методы в Null-классе и вставьте туда код из else части условия. После этого условный оператор можно будет вообще удалить, а разное поведение будет осуществляться за счёт полиморфизма.
    • Если не все так просто, и методы переопределить не получается, посмотрите, можно ли просто выделите операции, которые должны были выполняться при значении равном null в новые методы Null-объекта. Вызывайте эти методы вместо старого кода в else как операции по умолчанию.

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

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

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

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

Живой пример

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

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

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