Введення 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
ускладнюють і засмічують код.
Недоліки
- За відмову від умовних операторів ви розплачуєтесь ще одним новим класом.
Порядок рефакторингу
-
З потрібного вам класу створіть підклас, який виконуватиме роль Null-об’єкта.
-
В обох класах створіть метод
isNull()
, який повертатимеtrue
для Null-об’єкта іfalse
для об’єкта реального класу. -
Знайдіть усі місця, де код може повернути
null
замість реального об’єкта. Змініть цей код так, щоб він повертав Null-об’єкт. -
Знайдіть усі місця, де змінні реального класу порівнюються з
null
. Замініть такі перевірки викликом методуisNull()
. -
- Якщо в цих умовних операторах при значенні, не рівному `null`, виконуються методи початкового класу, перевизначить ці методи в Null-класі і вставте туди код з частини умови
else
. Після цього умовний оператор можна буде взагалі видалити, а різна поведінка здійснюватиметься за рахунок поліморфізму. - Якщо не все так просто, і методи перевизначити не виходить, подивіться, чи можна просто виділити операції, які повинні були виконуватися при значенні рівному
null
в нові методи Null-об’єкта. Викликайте ці методи замість старого коду вelse
як операції за умовчанням.
- Якщо в цих умовних операторах при значенні, не рівному `null`, виконуються методи початкового класу, перевизначить ці методи в Null-класі і вставте туди код з частини умови