# Null Object Pattern In der objektorientierten Programmierung ist ein *Null-Objekt* ein Objekt, das keinen Wert referenziert oder ein neutrales Verhalten aufweist. In vielen Programmiersprachen ist die Arbeit mit solchen Werten schwierig und kann entweder zu unerwarteten Fehlern führen, oder viele Überprüfungen erfordern. Im Jahr 1996 veröffentlichte Thomas Kühne ein Entwurfsmuster (engl. Design Pattern) um eine mögliche Lösung für dieses Problem zu bieten. Das ursprünglich "Void Value Pattern", heute auch "Null Object Pattern" genannte Entwurfsmuster, wollen wir uns in diesem Beitrag genauer ansehen. [^1] ## Motivation Objekte sind veränderbar (mutable) und ihre Werte werden oft geändert. Um eine normale Form der Kommunikation zu erreichen und dabei Effizienz zu bewahren, werden diese Objekte oft geteilt. Als Resultat verhalten sich Objekte ähnlich wie Pointers (zumindest in traditionelleren Sprachen). Anders als bei Letzteren, können Objekt-Pointer jedoch nicht auf beliebige Entitäten zeigen, sondern funktionieren mit polymorphen Entitäten. Ein Pointer kann also auf ein Objekt einer bestimmten Klasse und all deren Subklassen zeigen. Aus diesen Gründen werden sie auch *References* und nicht Pointers genannt. Gleich wie Pointer scheinen jedoch auch Referenzen noch ein antikes Relikt von ihren Pointer-Vorfahren zu haben: Einen "magischen" Standardwert, bekannt unter verschiedenen Namen wie *Null*, *None*, *Void* oder *Nil*. Dieser ist ein polymorpher Wert, der für alle Referenztypen besteht. Genutzt wird er dann, wenn eine Referenz kein Objekt referenziert. In den meisten Objekt-orientierten Programmiersprachen kann eine Objektreferenz also *null* sein. Wie bereits in der Einführung erwähnt, müssen diese Werte strikt geprüft werden, da (normalerweise) keine Methodenaufrufe auf *null*-Objekte möglich sind. Dieser Zustand kann also als eine Art von Exception betrachtet werden. Kühne hat bei seiner Arbeit hinterfragt, ob es einen Weg gibt die Nachteile eines allgemeinen *Void*-Typen zu umgehen. In seiner Lösung schlägt er vor, ob *Null* mit einem echten Wert, ursprünglich "Void Value" genannt, ersetzt werden könnte.[^2] ## Null Objects Das Design Pattern besagt folgendes: Anstelle einer *Null Reference* deklarieren wir die Abwesenheit eines Objekts durch ein Objekt, das die erwartete Schnittstelle implementiert, jedoch keine Methodenlogik enthält. Der Vorteil dabei ist, dass der Code nicht mehr von verschiedenen Konditionen abhängig ist und so fokussierter wie auch einfacher zu lesen ist. Ein weiterer Vorteil ist, dass bei einer expliziten Implementation des Null Typen keine unvorhersehbaren Seiteneffekte auftreten können und das Verhalten so sehr vorhersehbar wird. Angenommen, wir haben eine Funktion, die einen Ordner als Input erhält und die darin enthaltenen Dateien iteriert. Wenn der Ordner leer ist, würde diese Funktion einen Fehler auslösen. Es müsste also noch eine Überprüfung eingebaut werden, ob der Ordner überhaupt Dateien enthält. Wird stattdessen ein Null Objekt übergeben (z.B. eine leere Liste), dann führt die Funktion ihre normale Funktionsweise durch, wobei einfach keine Iteration stattfindet. Es ist auch weiterhin möglich zu prüfen, ob der Rückgabewert ein Null-Value ist und darauf unterschiedlich zu reagieren. ### Beispiel in C\# Um die Funktionsweise noch genauer zu verstehen, wollen wir das ganze an einem einfachen C#-Codebeispiel anschauen: Dafür wollen wir zuerst eine einfache Schnittstelle für Personen erstellen. Diese Schnittstelle hat eine abstrakte Implementation, von welcher anschliessend eine Klasse für Buchhalter erbt. ```csharp interface IPerson { void DoWork(); } abstract class Person : IPerson { public abstract void DoWork(); } class Accountant : Person { public override void DoWork() { Console.WriteLine("Accounting..."); } } ``` Die Verwendung dieser Struktur ist den meisten wahrscheinlich bereits bekannt. Für einen Buchhalter erstellen wir ein entsprechendes Objekt, welches dann auch "arbeiten" kann. Wenn wir jetzt jedoch eine Referenz mit `null` haben, wird ein Fehler geworfen, auf welchen wir reagieren müssen. ```csharp static class Program { static void Main() { IPerson accountant = new Accountant(); accountant.DoWork(); IPerson? unknown = null; try { /** Dieser Aufruf löst eine Exception aus **/ unknown.DoWork(); } catch(NullReferenceException) { /** Hier muss auf null reagiert werden, da ansonsten das Programm nicht mehr funktionsfähig ist. ***/ } } } ``` Um das Null Object Muster jetzt umzusetzen modifizieren wir unsere abstrakte Personenklasse um eine Null Implementation ohne Logik. Wir erstellen auch gleich ein Objekt dafür, welches über die Klasse verfügbar ist. ```csharp abstract class Person : IPerson { /** Ein Null Object wird erstellt, um dieses anstelle von null zu verwenden. **/ public static readonly IPerson Null = new NullPerson(); /** Eine eigene Null-Klasse ohne Funktionalität wird erstellt. **/ private class NullPerson : Person { public override void DoWork() { } } public abstract void DoWork(): } ``` Bei der Verwendung setzen wir unsere unbekannte Person jetzt auf diesen neuen Null-Wert. Wenn wir anschliessend versuchen, eine Methode darauf auszuführen, dann wird kein Fehler mehr geworfen. Stattdessen geschieht einfach nichts. ```csharp static class Program { static void Main() { IPerson accountant = new Accountant(); accountant.DoWork(); /** Anstelle von null wird das Null Objekt verwendet **/ IPerson unknown = Person.Null; /** Kein Fehler wird geworfen, es passiert einfach nichts **/ unknown.DoWork(); } } ``` ## Nachteile / Kritik Die Vorteile dieser Vorgehensweise haben wir bereits in der Einführung besprochen: Unser Code wird lesbarer und es geschieht kein unerwartetes Verhalten mehr. Wie bei allen solchen Mustern ist es jedoch keine universelle Lösung, sondern bringt auch Nachteile und entsprechende Kritik mit sich. Das Muster sollte grundsätzlich vorsichtig genutzt werden. Durch diese konzeptionelle Entkopplung von *Null* und einem Fehler können auch tatsächliche Fehler und Bugs als normale Programmdurchläufe erscheinen. Es ist auch zu beachten, dass die Nutzung oft mit viel Testen verbunden ist, da sichergestellt werden muss, dass im Programm nie *null* anstelle des Null-Objekts verwendet wird.[^1] ## Relevanz Die ursprüngliche Arbeit von Kühne wurde vor einer längeren Zeit veröffentlicht, was in Anbetracht der doch sehr signifikanten Evolution von Programmiersprachen die Frage aufwirft, ob dieses Muster heutzutage überhaupt noch von Relevanz ist. Diese Frage ist auch durchaus nicht einfach zu beantworten. Für diesen Beitrag wollen wir uns in der Gültigkeit auf die Sprache C# beschränken. So ist ein wichtiges Argument in dieser Diskussion die Features, welche eingeführt wurden, um dieses Problem zu adressieren. Mit C# 8.0 wurden beispielsweise Nullable Refernce Types eingeführt, die dem Compiler ermöglichen, eine Warnung anzugeben, wenn ein Entwickler keine richtige Prüfung für Null eingebaut hat. Auch Null-Conditional-Operatoren helfen dabei, in einer sehr einfachen Weise nur bei Gültigkeit der Referenz eine etwaige Methode auszuführen. Dazu kommen auch noch die Null-Coalescing-Operatoren, die nur bei Ungültigkeit der Referenz einen Wert überschreiben oder übergeben. In Anbetracht all dieser Entwicklungen fällt der Vorteil des Null Object Patterns sozusagen weg, während die Implikationen bestehen bleiben. Entsprechend lässt sich sagen, dass in Projekten mit neuen Versionen von C# das Null Object Pattern wahrscheinlich nicht nötig ist. Nichtsdestotrotz sei erwähnt, dass die Verwendung in spezifischen Situationen immer noch von Vorteil sein kann. Auch bei der Verwendung einer anderen Sprache muss diese Analyse individuell vorgenommen werden. Es lässt sich also sagen, dass dieses Entwurfsmuster nicht mehr von gleicher Relevanz ist wie zu der Zeit, in der es konzipiert wurde. ## Fazit Das Null Object Pattern bietet eine elegante Lösung, um mit Null-Referenzen in der objektorientierten Programmierung umzugehen. Anstatt überflüssige Null-Überprüfungen durchzuführen, ermöglicht es, die Abwesenheit eines Objekts durch ein spezielles Null-Objekt darzustellen, das die erwartete Schnittstelle implementiert, jedoch keine Logik enthält. Dies führt zu lesbarerem und robusterem Code, indem es unvorhersehbare Fehler vermeidet und das Verhalten des Programms vorhersehbar macht. Trotz seiner Vorteile, insbesondere in älteren Programmiersprachen oder spezifischen Anwendungsfällen, ist seine Relevanz in modernen C#-Projekten durch die Einführung von Nullable Reference Types und Null-Conditional-Operatoren gesunken. Dennoch bleibt das Prinzip eine wertvolle Ergänzung im Werkzeugkasten eines Entwicklers, wenn es ums Design von Software geht. [^1]: vgl. Kühne, Thomas: Nil and None considered Null and Void, 19.05.2025, https://homepages.ecs.vuw.ac.nz/~tk/publications/papers/void.pdf [^2]: vgl. Null object pattern, in: Wikipedia, 19.05.2025, https://en.wikipedia.org/wiki/Null_object_pattern