Patrón de objeto nulo

format_list_bulleted Contenido keyboard_arrow_down
ImprimirCitar

En la programación informática orientada a objetos, un objeto nulo es un objeto sin valor referenciado o con un comportamiento neutro definido (nulo). El patrón de diseño de objetos nulos, que describe los usos de dichos objetos y su comportamiento (o falta de él), se publicó por primera vez como "Valor nulo" y más tarde en la serie de libros Lenguajes de patrones de diseño de programas como "Objeto nulo".

Motivación

En la mayoría de los lenguajes orientados a objetos, como Java o C#, las referencias pueden ser nulas. Es necesario comprobar que estas referencias no sean nulas antes de invocar ningún método, ya que, por lo general, los métodos no se pueden invocar en referencias nulas.

El lenguaje Objective-C adopta otro enfoque para este problema y no hace nada cuando envía un mensaje a nil; si se espera un valor de retorno, se devuelve nil (para objetos), 0 (para valores numéricos), NO (para valores BOOL) o una estructura (para tipos de estructura) con todos sus miembros inicializados a null/0/NO/estructura inicializada a cero.

Descripción

En lugar de utilizar una referencia nula para transmitir la ausencia de un objeto (por ejemplo, un cliente inexistente), se utiliza un objeto que implementa la interfaz esperada, pero cuyo cuerpo de método está vacío. Un objetivo clave de utilizar un objeto nulo es evitar condicionales de diferentes tipos, lo que da como resultado un código más centrado y más rápido de leer y seguir, es decir, una mejor legibilidad. Una ventaja de este enfoque sobre una implementación predeterminada funcional es que un objeto nulo es muy predecible y no tiene efectos secundarios: no hace nada.

Por ejemplo, una función puede recuperar una lista de archivos en una carpeta y realizar alguna acción en cada uno de ellos. En el caso de una carpeta vacía, una respuesta puede ser lanzar una excepción o devolver una referencia nula en lugar de una lista. Por lo tanto, el código que espera una lista debe verificar que de hecho la tiene antes de continuar, lo que puede complicar el diseño.

Si, en cambio, se devuelve un objeto nulo (es decir, una lista vacía), no es necesario verificar que el valor de retorno sea, de hecho, una lista. La función que realiza la llamada puede simplemente iterar la lista de forma normal, sin hacer nada en realidad. Sin embargo, aún es posible verificar si el valor de retorno es un objeto nulo (una lista vacía) y reaccionar de forma diferente si se desea.

El patrón de objeto nulo también se puede utilizar para actuar como un código auxiliar para realizar pruebas, si una determinada característica, como una base de datos, no está disponible para realizar pruebas.

Ejemplo

Dado un árbol binario, con esta estructura de nodos:

clase node
nodo izquierdo
Noderecho
}

Se puede implementar un procedimiento de tamaño de árbol de forma recursiva:

función árbol_size(nodo) {
volver 1 + árbol_size(node.left) + árbol_size(node.right)
}

Dado que los nodos secundarios pueden no existir, se debe modificar el procedimiento agregando comprobaciones de inexistencia o nulas:

función árbol_size(nodo) {
suma fija = 1
 si node.left existe {
suma = suma + árbol_size(node.left)
}
 si node.right existe {
suma = suma + árbol_size(node.right)
}
de retorno
}

Sin embargo, esto hace que el procedimiento sea más complicado al mezclar comprobaciones de límites con lógica normal, y se vuelve más difícil de leer. Al utilizar el patrón de objeto nulo, se puede crear una versión especial del procedimiento, pero solo para nodos nulos:

función árbol_size(nodo) {
volver 1 + árbol_size(node.left) + árbol_size(node.right)
}
función árbol_size(null_node) {
retorno 0
}

Esto separa la lógica normal del manejo de casos especiales y hace que el código sea más fácil de entender.

Relación con otros patrones

Puede considerarse un caso especial del patrón Estado y del patrón Estrategia.

No es un patrón de Design Patterns, pero se menciona en Refactoring de Martin Fowler y en Refactoring To Patterns de Joshua Kerievsky como la refactorización Insert Null Object.

El capítulo 17 de Desarrollo ágil de software: principios, patrones y prácticas de Robert Cecil Martin está dedicado a este patrón.

Alternativas

A partir de C# 6.0 es posible utilizar el operador "?." (también conocido como operador condicional nulo), que simplemente se evaluará como nulo si su operando izquierdo es nulo.

// compilar como aplicación de consola, requiere C# 6.0 o superiorutilizando Sistema;namespace ConsoleApplication2{} clase Programa {} estática vacío Main()cuerda[] args) {} cuerda str = "prueba";  Consol.WriteLine()str?Duración); Consol.Leer Clave(); } }}// La salida será:// 4

Métodos de extensión y coalesificación Null

En algunos lenguajes de Microsoft.NET, los métodos de extensión se pueden utilizar para realizar lo que se denomina "fusión de valores nulos". Esto se debe a que los métodos de extensión se pueden llamar en valores nulos como si se tratara de una "invocación de método de instancia", mientras que, de hecho, los métodos de extensión son estáticos. Se puede hacer que los métodos de extensión comprueben los valores nulos, lo que libera al código que los utiliza de tener que hacerlo. Tenga en cuenta que el ejemplo siguiente utiliza el operador de fusión de valores nulos de C# para garantizar una invocación sin errores, donde también podría haberse utilizado un if...then...else más mundano. El siguiente ejemplo solo funciona cuando no le importa la existencia de valores nulos o trata los valores nulos y las cadenas vacías de la misma manera. Es posible que la suposición no se cumpla en otras aplicaciones.

// compilar como aplicación de consola, requiere C# 3.0 o superiorutilizando Sistema;utilizando System.Linq;namespace MyExtensionConExample {} público estática clase StringExtensions {}  público estática int SafeGetLength()esto cuerda valorOrNull) {}  Regreso ()valorOrNull ? cuerda.Vacío).Duración;  } } público estática clase Programa {} // definir algunas cadenas estática solo cuerda[] cuerdas = nuevo [] {} "Sr. X.", "Katrien Duck", nulo, "Q" }; // escribir la longitud total de todas las cadenas en el array público estática vacío Main()cuerda[] args) {} Var query = desde texto dentro cuerdas seleccionar texto.SafeGetLength(); // no es necesario hacer ningún cheque aquí Consol.WriteLine()query.Sum()); } }}// La salida será:// 18

En varios idiomas

C++

Un lenguaje con referencias a objetos tipificadas estáticamente ilustra cómo el objeto nulo se convierte en un patrón más complicado:

#include ■iostreamclase Animal {} público: virtual ~Animal() = por defecto; virtual vacío Sonido() const = 0;};clase Perro : público Animal {} público: virtual vacío Sonido() const Anulación {} std::cout c) c) "¡woof!" c) c) std::endl; }};clase NullAnimal : público Animal {} público: virtual vacío Sonido() const Anulación {}};

Aquí, la idea es que hay situaciones en las que se requiere un puntero o una referencia a un objeto Animal, pero no hay ningún objeto apropiado disponible. Una referencia nula es imposible en C++ que cumple con los estándares. Un puntero nulo Animal* es posible y podría ser útil como marcador de posición, pero no se puede usar para el envío directo: a->MakeSound() es un comportamiento indefinido si a es un puntero nulo.

El patrón de objeto nulo resuelve este problema al proporcionar una clase especial NullAnimal que se puede instanciar vinculada a un puntero o referencia Animal.

La clase nula especial debe crearse para cada jerarquía de clases que deba tener un objeto nulo, ya que un NullAnimal no sirve cuando lo que se necesita es un objeto nulo con respecto a alguna clase base Widget que no esté relacionada con la jerarquía Animal.

Tenga en cuenta que NO tener una clase nula es una característica importante, a diferencia de los lenguajes donde "todo es una referencia" (por ejemplo, Java y C#). En C++, el diseño de una función o método puede indicar explícitamente si se permite o no el valor nulo.

// Función que requiere una instancia TENAnimal, y no aceptará nula.vacío DoSomething()const Animal" animal) {} // ← puede que nunca sea null aquí.}// Función que puede aceptar una instancia o nula TENAnimal.vacío DoSomething()const Animal* animal) {} // ← puede ser null.}

C#

C# es un lenguaje en el que se puede implementar correctamente el patrón de objeto nulo. Este ejemplo muestra objetos animales que muestran sonidos y una instancia de NullAnimal utilizada en lugar de la palabra clave null de C#. El objeto nulo proporciona un comportamiento coherente y evita una excepción de referencia nula en tiempo de ejecución que se produciría si se utilizara la palabra clave null de C# en su lugar.

/* Aplicación del patrón de objetos nulos: */utilizando Sistema;// La interfaz animal es la clave para la compatibilidad para las implementaciones Animal a continuación.interfaz IAnimal{}vacío Sonido();}// Animal es el caso base.abstracto clase Animal : IAnimal{}// Una instancia compartida que se puede utilizar para las comparacionespúblico estática solo IAnimal Null = nuevo NullAnimal();// El caso Null: esta clase NullAnimal debe ser usada en lugar de la palabra clave null C#.privado clase NullAnimal : Animal{}público Anulación vacío Sonido(){}// Purposefully provides no behaviour.}}público abstracto vacío Sonido();}El perro es un animal real.clase Perro : Animal{}público Anulación vacío Sonido(){}Consol.WriteLine()"¡Vaya!");}}/* ====================================================================== * Ejemplo de uso simplista en un punto de entrada principal. */estática clase Programa{}estática vacío Main(){}IAnimal perro = nuevo Perro();perro.Sonido(); // salidas "Woof!"/* En lugar de usar C# null, use el Animal. Un caso nulo. * Este ejemplo es simplista pero transmite la idea de que si el animal. instancia Null se utiliza entonces el programa * nunca experimentará un.NET System. NullReferenceExcepción en tiempo de ejecución, a diferencia de si se utilizaron null C#. */IAnimal desconocida = Animal.Null; // realizadas reemplaza: IAnimal unknown = null;desconocida.Sonido(); // no produce nada, pero no lanza una excepción de tiempo de ejecución }}

Smalltalk

Siguiendo el principio de Smalltalk, todo es un objeto, la ausencia de un objeto es modelada por un objeto, llamado nil. En GNU Smalltalk, por ejemplo, la clase de nil es UndefinedObject, un descendiente directo de Object.

Cualquier operación que no devuelva un objeto sensato para su propósito puede devolver nil en su lugar, evitando así el caso especial de devolver "ningún objeto" que no es compatible con los diseñadores de Smalltalk. Este método tiene la ventaja de la simplicidad (no es necesario un caso especial) sobre el enfoque clásico de "nulo" o "ningún objeto" o "referencia nula". Los mensajes especialmente útiles para usar con nil son isNil, ifNil: o ifNotNil:, que hacen que sea práctico y seguro tratar con posibles referencias a nil en programas de Smalltalk.

Lisp común

En Lisp, las funciones pueden aceptar sin problemas el objeto especial nil, lo que reduce la cantidad de pruebas de casos especiales en el código de la aplicación. Por ejemplo, aunque nil es un átomo y no tiene ningún campo, las funciones car y cdr aceptan nil y simplemente lo devuelven, lo que es muy útil y da como resultado un código más corto.

Dado que nil es la lista vacía en Lisp, la situación descrita en la introducción anterior no existe. El código que devuelve nil devuelve lo que de hecho es la lista vacía (y nada que se parezca a una referencia nula a un tipo de lista), por lo que el que llama no necesita probar el valor para ver si tiene o no una lista.

El patrón de objeto nulo también se admite en el procesamiento de valores múltiples. Si el programa intenta extraer un valor de una expresión que no devuelve ningún valor, el comportamiento es que se sustituye el objeto nulo nil. Por lo tanto, (list (values)) devuelve (nil) (una lista de un elemento que contiene nil). La expresión (values) no devuelve ningún valor, pero como la llamada de función a list necesita reducir su expresión de argumento a un valor, el objeto nulo se sustituye automáticamente.

CLOS

En Common Lisp, el objeto nil es la única instancia de la clase especial null. Esto significa que un método puede especializarse en la clase null, implementando así el patrón de diseño null. Es decir, está esencialmente integrado en el sistema de objetos:

; clase de perro vacía()defclass perro () ();; un objeto perro hace un sonido al ladrar: woof! se imprime en la salida estándar;; cuando se llama (make-sound x), si x es una instancia de la clase del perro.()defmethod Sonido (()obj perro) ()formato t "woof!"); permitir (núcleo de sonido) trabajar a través de la especialización a la clase nula.;; cuerpo vacío innocuo: nil no hace ruido.()defmethod Sonido (()obj nulo))

La clase null es una subclase de la clase symbol, porque nil es un símbolo. Dado que nil también representa la lista vacía, null también es una subclase de la clase list. Los parámetros de los métodos especializados en symbol o list tomarán un argumento nil. Por supuesto, todavía se puede definir una especialización null que sea una coincidencia más específica para nil.

Plan

A diferencia de Common Lisp y muchos dialectos de Lisp, el dialecto Scheme no tiene un valor nulo que funcione de esta manera; las funciones car y cdr no se pueden aplicar a una lista vacía; por lo tanto, el código de aplicación de Scheme tiene que usar las funciones de predicado empty? o pair? para evitar esta situación, incluso en situaciones en las que un Lisp muy similar no necesitaría distinguir los casos vacíos y no vacíos gracias al comportamiento de nil.

Ruby

En lenguajes tipados como Ruby, la herencia del lenguaje no es necesaria para proporcionar el comportamiento esperado.

clase Perro def sonido "bark" finalfinal clase NilAnimal def sonido()*); finalfinaldef get_animal()animal=NilAnimal.nuevo) animalfinalget_animal()Perro.nuevo).sonido = "bark"get_animal.sonido = Nil

Los intentos de aplicar parches directamente a NilClass en lugar de proporcionar implementaciones explícitas tienen más efectos secundarios inesperados que beneficios.

JavaScript

En lenguajes tipados como JavaScript, la herencia del lenguaje no es necesaria para proporcionar el comportamiento esperado.

clase Perro {} sonido() {} Regreso 'bark '; }}clase NullAnimal {} sonido() {} Regreso nulo; }}función getAnimal()Tipo) {} Regreso Tipo == Perro ' ? nuevo Perro() : nuevo NullAnimal();}[Perro ', nulo].mapa(()animal) = getAnimal()animal).sonido());// Devoluciones [bark", null]

Java

público interfaz Animal {}vacío Sereno();}público clase Perro implementos Animal {}público vacío Sereno() {}Sistema.Fuera..println()"¡woof!");}}público clase NullAnimal implementos Animal {}público vacío Sereno() {} // silencio...}}

Este código ilustra una variación del ejemplo de C++ anterior, utilizando el lenguaje Java. Al igual que con C++, se puede crear una instancia de una clase nula en situaciones en las que se requiere una referencia a un objeto Animal, pero no hay ningún objeto apropiado disponible. Es posible crear un objeto Animal nulo (Animal myAnimal = null;) y podría ser útil como marcador de posición, pero no se puede utilizar para llamar a un método. En este ejemplo, myAnimal.makeSound(); generará una NullPointerException. Por lo tanto, puede ser necesario código adicional para comprobar si hay objetos nulos.

El patrón de objeto nulo resuelve este problema al proporcionar una clase especial NullAnimal que se puede instanciar como un objeto de tipo Animal. Al igual que con C++ y lenguajes relacionados, esa clase nula especial se debe crear para cada jerarquía de clases que necesite un objeto nulo, ya que un NullAnimal no sirve cuando lo que se necesita es un objeto nulo que no implemente la interfaz Animal.

PHP

interfaz Animal{} público función Sereno();}clase Perro implementos Animal{} público función Sereno() {} eco "Woof...\n"; }}clase Gato implementos Animal{} público función Sereno() {} eco "Meowww...\n"; }}clase NullAnimal implementos Animal{} público función Sereno() {} // silencio... }}$animal Tipo = Elefante ';función makeAnimalfromAnimalType()cuerda $animal Tipo): Animal{} interruptor ()$animal Tipo) {} Caso Perro ': Regreso nuevo Perro(); Caso 'cat ': Regreso nuevo Gato(); por defecto: Regreso nuevo NullAnimal(); }}makeAnimalfromAnimalType()$animal Tipo)- No.Sereno(); El animal nulo no hace ruidofunción animalMakeSound()Animal $animal): vacío{} $animal- No.Sereno();}en adelante ([2] makeAnimalfromAnimalType()Perro '), makeAnimalfromAnimalType()'NullAnimal '), makeAnimalfromAnimalType()'cat '), ] como $animal) {} // Eso también reduce el código de manejo nulo animalMakeSound()$animal);}

Visual Basic.NET

La siguiente implementación del patrón de objeto nulo demuestra que la clase concreta proporciona su objeto nulo correspondiente en un campo estático Empty. Este enfoque se utiliza con frecuencia en .NET Framework (String.Empty, EventArgs.Empty, Guid.Empty, etc.).

Público Clase Animal Público Compartida ReadOnly Vacío As Animal = Nuevo AnimalEmpty() Público Sobresechable Subsidio Sonido() Consol.WriteLine()"¡Vaya!") Final SubsidioFinal ClaseAmigo No hereditario Clase AnimalEmpty Herederos Animal Público Anulaciones Subsidio Sonido() '  Final SubsidioFinal Clase

Crítica

Este patrón debe utilizarse con cuidado, ya que puede hacer que aparezcan errores o fallos durante la ejecución normal del programa.

Se debe tener cuidado de no implementar este patrón solo para evitar verificaciones nulas y hacer que el código sea más legible, ya que el código más difícil de leer puede simplemente moverse a otro lugar y ser menos estándar, como cuando se debe ejecutar una lógica diferente en caso de que el objeto proporcionado sea de hecho el objeto nulo. El patrón común en la mayoría de los lenguajes con tipos de referencia es comparar una referencia con un único valor denominado null o nil. Además, existe la necesidad adicional de probar que ningún código en ninguna parte asigne nunca null en lugar del objeto nulo, porque en la mayoría de los casos y lenguajes con tipado estático, esto no es un error del compilador si el objeto nulo es de un tipo de referencia, aunque ciertamente conduciría a errores en tiempo de ejecución en partes del código donde se utilizó el patrón para evitar verificaciones nulas. Además de eso, en la mayoría de los lenguajes y asumiendo que puede haber muchos objetos nulos (es decir, el objeto nulo es un tipo de referencia pero no implementa el patrón singleton de una u otra manera), verificar el objeto nulo en lugar del valor nulo o nulo introduce una sobrecarga, al igual que probablemente lo hace el patrón singleton en sí mismo al obtener la referencia singleton.

Véase también

  • Tipo de relleno
  • Tipo de opción

Referencias

  1. ^ Kühne, Thomas (1996). "Void Value". Proceedings of the First International Conference on Object-Oriented Technology, White Object-Oriented Nights 1996 (WOON'96), St. Petersburg, Russia.
  2. ^ Woolf, Bobby (1998). "Null Object". En Martin, Robert; Riehle, Dirk; Buschmann, Frank (eds.). Patrón Idiomas de Diseño del Programa 3. Addison-Wesley.
  3. ^ "Trabajar con objetos (Trabajar con nil)". iOS Developer Library. Apple, Inc. 2012-12-13. Retrieved 2014-05-19.
  4. ^ Fowler, Martin (1999). Refactoring. Mejora del diseño del código existente. Addison-Wesley. ISBN 0-201-48567-2.
  5. ^ Kerievsky, Joshua (2004). Refactoring To Patterns. Addison-Wesley. ISBN 0-321-21335-1.
  6. ^ Martin, Robert (2002). Desarrollo de software ágil: principios, patrones y prácticas. Pearson Education. ISBN 0-13-597444-5.
  7. ^ Fowler, Martin (1999). Refactoring, p. 216.
  • Jeffery Walker cuenta del Patrón de Objetos Null
  • Martin Fowler descripción del caso especial, un patrón ligeramente más general
  • Null Object Pattern Revisited
  • Introduce Null Refactorización de objetos
  • SourceMaking Tutorial
  • Patrón de Objetos Null en Swift
Más resultados...
Tamaño del texto:
undoredo
format_boldformat_italicformat_underlinedstrikethrough_ssuperscriptsubscriptlink
save