Глянь мій новий курс по Git! Привіт! Глянь мій новий курс по Git! Привіт! Глянь мій новий курс по Git на GitByBit.com! Привіт! Хочеш класно освіжити Git? Глянь мій новий курс на GitByBit.com!

Введення Null-об'єкта

Також відомий як: Introduce Null Object

Проблема

Через те, що деякі методи повертають 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 {
  public function isNull() {
    return true;
  }
  public 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 is 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 or 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 {
  isNull(): boolean {
    return true;
  }
  getPlan(): Plan {
    return new NullPlan();
  }
  // Some other NULL functionality.
}

// Replace null values with Null-object.
let customer = (order.customer != null) ?
  order.customer : new 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 як операції за умовчанням.