Null object (шаблон проєктування)
В об'єктно-орієнтованому програмуванні, Null Object або нульовий об'єкт — це об'єкт з визначеною нейтральною (англ. null) поведінкою. Шаблон проєктування Null Object описує використання цих об'єктів та їх поведінки або відсутності таких. Вперше цей шаблон було описано в серії книжок Pattern Languages of Program Design.[1]
У більшості об'єктно-орієнтованих мов програмування, таких як Java або C#, посилання можуть приймати значення null. Це значення говорить про те, що за посиланням не існує реального об'єкту і виклик методів може призвести до численних помилок та краху системи.
Традиційно, методи, що повертають об'єкти (наприклад, методи твірних шаблонів) у випадку невдачі (наприклад, пошук об'єкту в порожній колекції) повинні повертати коректне значення посилання — null
.
Обробка цього результату може бути реалізована простою перевіркою посилання на null
перед викликом методів.
// Приклад на Java
class Animal {
public void makeSound() { System.out.write("Гав-гав!"); }
}
...
Animal pet = animalsProvider.getAnimal();
if (pet != null) {
pet.makeSound();
} else {
/* обробка помилки */
}
...
Проблему обробки null
-посилань і вирішує шаблон Null object.
Треба відзначити, що, наприклад, у мові програмування Objective-C використовується інший підхід до цієї проблеми: всі методи, що викликаються через nil
-посилання (nil
близька за сенсом до null частина Objective-C) повертають також nil
.
Замість використання null
-посилання для відображення відсутності об'єкту треба використовувати спеціальний об'єкт, який реалізує потрібні інтерфейси, але не має поведінки, тобто має пусті методи. Перевагою цього підходу є те, що реалізація нульового об'єкту завжди передбачувана: вона нічого не робить, а тому не має таких побічних ефектів, які має null
-посилання.
Наприклад, функція повинна прочитати список файлів в директорії та виконати якісь дії з кожним. В випадку, якщо директорія порожня, можна повернути null
або згенерувати виняток. Таким чином, код, що очікує список файлів повинен перевіряти, чи дійсно він отримав список, а це в свою чергу ускладнює структуру програми.
Якщо повертати нульовий об'єкт (наприклад, порожній список), то зникає необхідність перевірок. Викликаючий код може ітерувати список файлів, не зважаючи на можливі помилки і не виконувати ніяких дій при цьому.
Хоча, залишається можливим робити дещо змінену перевірку: «чи дорівнює результат нульовому об'єкту?», та діяти далі за логікою програми.
Також, цей шаблон може використовуватися як заглушка при тестуванні.
Шаблон може розглядатися як спеціальний випадок шаблонів Стан (англ. State) та Стратегія (англ. Strategy). Він не написаний в книзі GoF, а був запропонований Мартіном Фаулером[2] та Джошуа Керієвскі[3].
Мова зі статично типізованими посиланнями на об'єкти показує, як нульовий об'єкт стає складнішим шаблоном.
class animal {
public:
virtual void make_sound() = 0;
};
class dog : public animal {
void make_sound() { cout << "woof!" << endl; }
};
class null_animal : public animal {
void make_sound() { }
};
Існують ситуації, коли вказівник або посилання на об'єкт класу animal
необхідний, але за ним немає належного об'єкту. Посилання не може бути null
, коли вказівник animal *
— може: такий вказівник можна використовувати для зберігання об'єкту, але неможливо безпосередньо викликати методи. Код a->make_sound()
викличе помилку undefined behavior (укр. невизначена поведінка), якщо a
буде null
-вказівником.
Шаблон Null object вирішує цю проблему, впроваджуючи спеціальний клас null_animal
, який може бути інстанційований та викорстовуватись з вказівником чи посиланням на animal
.
С# є мовою, в якій шаблон Null object можна реалізувати канонічно. В нижче наведеному прикладі, нульовий об'єкт реалізує очікувану порожню поведінку та попереджає проблеми часу виконання програми, а сам виняток Null Reference Exception, який буде згенеровано, якщо відбудеться використання null
-посилання.
using System;
interface Animal
{
void MakeSound();
}
class Dog : Animal
{
public void MakeSound()
{
Console.WriteLine("Woof!");
}
}
class NullAnimal : Animal
{
public void MakeSound()
{
}
}
static class Program
{
static void Main()
{
Animal dog = new Dog();
dog.MakeSound(); //
Animal unknown = new NullAnimal(); //<< замінює: Animal unknown = null;
unknown.MakeSound(); // нічого не відбувається
}
}
За принципом Smalltalk, «все є об'єктом», відсутність якогось об'єкту моделюється об'єктом nil
. В GNU Smalltalk, наприклад, nil
є об'єктом класу UndefinedObject
, прямого нащадка Object
.
Будь-яка операція, яка не змогла повернути потрібний об'єкт може повернути nil
замість нього, таким чином, попереджаючи повернення «об'єкту нема». Цей підхід спрощує програму, позбавляючи від null-посилань, null-вказівників.
В Ruby нульовий об’єкт це повноцінний об’єкт класу NilClass. Він специфічним чином працює в логічних виразах: він приймає значення false. Ruby дозволяє розширювати цей клас, тому, якщо ви хочете таку ж поведінку, як у в Objective-C, тоді використовуйте щось на кшталт цього:
class NilClass
def method_missing(*)
return nil
end
end
- Jeffrey Walkers' account of the Null Object Pattern [Архівовано 6 червня 2012 у Wayback Machine.]
- Martin Fowlers' description of Special Case, a slightly more general pattern [Архівовано 16 травня 2012 у Wayback Machine.]
- Null Object Pattern Revisited [Архівовано 10 квітня 2010 у Wayback Machine.]
- Introduce Null Object refactoring [Архівовано 30 травня 2012 у Wayback Machine.]
- SourceMaking Tutorial [Архівовано 15 червня 2012 у Wayback Machine.]
- ↑ Woolf, Bobby (1998). Null Object. У Martin, Robert; Riehle, Dirk; Buschmann, Frank (ред.). Pattern Languages of Program Design 3. Addison-Wesley.
- ↑ Fowler, Martin (1999). Refactoring. Improving the Design of Existing Code. Addison-Wesley. ISBN 0-201-48567-2.
- ↑ Kerievsky, Joshua (2004). Refactoring To Patterns. Addison-Wesley. ISBN 0-321-21335-1.