Введение 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
как операции по умолчанию.
- Если в этих условных операторах при значении не равном