En la programación de computadoras 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 objeto nulo describe los usos de dichos objetos y su comportamiento (o falta de él). Se publicó por primera vez en la serie de libros Pattern Languages of Program Design . [1]
Motivación
En la mayoría de los lenguajes orientados a objetos, como Java o C # , las referencias pueden ser nulas . Estas referencias deben verificarse para asegurarse de que no sean nulas antes de invocar cualquier método , porque los métodos normalmente no se pueden invocar en referencias nulas.
El lenguaje Objective-C adopta otro enfoque para este problema y no hace nada al enviar un mensaje a nil
; si se espera un valor de retorno, nil
(para objetos), 0 (para valores numéricos), NO
(para BOOL
valores), o una estructura (para tipos de estructura) con todos sus miembros inicializados en null
/ 0 / NO
/ estructura inicializada cero se devuelve. [2]
Descripción
En lugar de usar una referencia nula para transmitir la ausencia de un objeto (por ejemplo, un cliente inexistente), se usa un objeto que implementa la interfaz esperada, pero cuyo cuerpo del método está vacío. La ventaja de este enfoque sobre una implementación predeterminada de trabajo 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. 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 efectivamente tiene una antes de continuar, lo que puede complicar el diseño.
Al devolver un objeto nulo (es decir, una lista vacía) en su lugar, no es necesario verificar que el valor de retorno sea de hecho una lista. La función de llamada puede simplemente iterar la lista de forma normal, sin hacer nada. Sin embargo, aún es posible verificar si el valor de retorno es un objeto nulo (una lista vacía) y reaccionar de manera diferente si se desea.
El patrón de objeto nulo también se puede utilizar para actuar como un código auxiliar para la prueba, si una determinada característica, como una base de datos, no está disponible para la prueba.
Ejemplo
Dado un árbol binario , con esta estructura de nodo:
nodo de clase { nodo a la izquierda nodo derecho}
Se puede implementar un procedimiento de tamaño de árbol de forma recursiva:
función tamaño_árbol (nodo) { return 1 + tamaño_árbol (node.left) + tamaño_árbol (node.right)}
Dado que los nodos secundarios pueden no existir, se debe modificar el procedimiento agregando verificaciones de no existencia o nulas:
función tamaño_árbol (nodo) { establecer suma = 1 si existe node.left { suma = suma + tamaño_árbol (nodo.izquierda) } si existe node.right { suma = suma + tamaño_árbol (nodo.derecha) } suma de devolución}
Sin embargo, esto hace que el procedimiento sea más complicado al mezclar las verificaciones de límites con la lógica normal, y se vuelve más difícil de leer. Usando el patrón de objeto nulo, se puede crear una versión especial del procedimiento pero solo para nodos nulos:
función tamaño_árbol (nodo) { return 1 + tamaño_árbol (node.left) + tamaño_árbol (node.right)}
función tamaño_árbol (nodo_nulo) { volver 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 como un caso especial del patrón de Estado y del patrón de Estrategia .
No es un patrón de Design Patterns , pero se menciona en Refactoring [3] de Martin Fowler y Refactoring To Patterns [4] de Joshua Kerievsky como la refactorización de Insert Null Object .
El capítulo 17 de Desarrollo ágil de software de Robert Cecil Martin : principios, patrones y prácticas [5] está dedicado al patrón.
Alternativas
Desde C # 6.0 es posible usar 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 superior usando System ;espacio de nombres ConsoleApplication2 { class Program { static void Main ( string [] args ) { string str = "prueba" ; Consola . WriteLine ( cadena ?. Longitud ); Consola . ReadKey (); } } } // La salida será: // 4
Métodos de extensión y fusión nula
En algunos lenguajes de Microsoft .NET , los métodos de extensión se pueden utilizar para realizar lo que se denomina 'fusión nula'. 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 pueden hacer métodos de extensión para verificar valores nulos, liberando así el código que los usa de tener que hacerlo. Tenga en cuenta que el siguiente ejemplo utiliza el operador de fusión C # Null para garantizar una invocación sin errores, donde también podría haber utilizado un if ... then ... else más mundano. El siguiente ejemplo solo funciona cuando no le importa la existencia de un valor nulo, o si trata la cadena nula y vacía de la misma manera. Es posible que la suposición no sea válida en otras aplicaciones.
// compilar como aplicación de consola, requiere C # 3.0 o superior usando System ; utilizando System.Linq ; namespace MyExtensionWithExample { public static class StringExtensions { public static int SafeGetLength ( esta cadena valueOrNull ) { return ( valueOrNull ?? string . Empty ). Longitud ; } } Public static class Program { // define algunas cadenas static readonly string [] strings = new [] { "Mr X." , "Katrien Duck" , nulo , "Q" }; // escribe la longitud total de todas las cadenas en la matriz public static void Main ( string [] args ) { var query = from text in strings select text . SafeGetLength (); // no es necesario hacer ninguna comprobación aquí Consola . WriteLine ( consulta . Sum ()); } } } // La salida será: // 18
En varios idiomas
C ++
Un lenguaje con referencias a objetos escritas estáticamente ilustra cómo el objeto nulo se convierte en un patrón más complicado:
#include clase Animal { público : virtual ~ Animal () = predeterminado ; virtual void MakeSound () const = 0 ; };class Dog : public Animal { public : virtual void MakeSound () const override { std :: cout << "¡guau!" << std :: endl ; } };class NullAnimal : public Animal { public : virtual void MakeSound () const override {} };
Aquí, la idea es que hay situaciones en las que Animal
se requiere un puntero o una referencia a un objeto, pero no hay un objeto apropiado disponible. Una referencia nula es imposible en C ++ conforme al estándar. Un Animal*
puntero nulo 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 proporcionando una NullAnimal
clase especial de la que se puede crear una instancia vinculada a un Animal
puntero o referencia.
La clase especial nula debe crearse para cada jerarquía de clases que va a tener un objeto nulo, ya que a NullAnimal
no sirve cuando lo que se necesita es un objeto nulo con respecto a alguna Widget
clase base que no está relacionada con la Animal
jerarquía.
Tenga en cuenta que NO tener una clase nula en absoluto es una característica importante, en contraste con los lenguajes donde "cualquier cosa 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 nulo.
// Función que requiere un | Animal | instancia, y no aceptará nulos. void DoSomething ( const Animal & animal ) { // | animal | puede que nunca sea nulo aquí. }// Función que puede aceptar un | Animal | instancia o nulo. void DoSomething ( const Animal * animal ) { // | animal | puede ser nulo. }
C#
C # es un lenguaje en el que el patrón de objeto nulo se puede implementar correctamente. Este ejemplo muestra objetos de 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 en su lugar se utilizara la palabra clave nula de C #.
/ * Implementación de patrón de objeto nulo: * / using System ;// La interfaz Animal es la clave para la compatibilidad de las implementaciones de Animal a continuación. interfaz IAnimal { void MakeSound (); }// Animal es el caso base. clase abstracta Animal : IAnimal { // Una instancia compartida que se puede utilizar para comparaciones public static readonly IAnimal Null = new NullAnimal (); // El caso nulo: esta clase NullAnimal debe usarse en lugar de la palabra clave nula de C #. private class NullAnimal : Animal { public override void MakeSound () { // No proporciona ningún comportamiento a propósito . } } public abstract void MakeSound (); }// El perro es un animal real. clase Perro : IAnimal { public void MakeSound () { Console . WriteLine ( "¡ Guau !" ); } }/ * ========================= * Ejemplo de uso simplista en un punto de entrada principal. * / clase estática Programa { vacío estático Main () { IAnimal dog = new Dog (); perro . MakeSound (); // genera "¡Guau!" / * En lugar de usar C # null, use la instancia Animal.Null. * Este ejemplo es simplista pero transmite la idea de que si se usa la instancia Animal.Null, el programa * nunca experimentará una excepción System.NullReferenceException .NET en tiempo de ejecución, a diferencia de si se usara C # null. * / IAnimal desconocido = Animal . Nulo ; // << reemplaza: IAnimal desconocido = nulo; desconocido . MakeSound (); // no genera nada, pero no genera una excepción de tiempo de ejecución } }
Charla
Siguiendo el principio de Smalltalk, todo es un objeto , la ausencia de un objeto es modelada en sí misma 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 sensible para su propósito puede regresar nil
en su lugar, evitando así el caso especial de devolver "ningún objeto" sin el apoyo de los diseñadores de Smalltalk. Este método tiene la ventaja de la simplicidad (sin necesidad de un caso especial) sobre el enfoque clásico "nulo" o "sin objeto" o "referencia nula". Los mensajes especialmente útiles para usar nil
son isNil
, ifNil:
o ifNotNil:
,, que hacen que sea práctico y seguro tratar con posibles referencias nil
en los programas de Smalltalk.
Lisp común
En Lisp, las funciones pueden aceptar elegantemente 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 campos, las funciones car
y lo cdr
aceptan nil
y lo devuelven, lo cual 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 no nada parecido a una referencia nula a un tipo de lista), por lo que la persona 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 nil
se sustituye el objeto nulo . Por lo tanto, (list (values))
devuelve (nil)
(una lista de un elemento que contiene nil). La (values)
expresión no devuelve ningún valor, pero dado que la llamada a la función list
necesita reducir su expresión de argumento a un valor, el objeto nulo se sustituye automáticamente.
CERRAR
En Common Lisp, el objeto nil
es la única instancia de la clase especial null
. Lo que esto significa es que un método puede especializarse en la null
clase, implementando así el patrón de diseño nulo. Es decir, está esencialmente integrado en el sistema de objetos:
;; clase de perro vacía( defclass perro () ());; un objeto de perro emite un sonido al ladrar: ¡guau! se imprime en salida estándar ;; cuando se llama a (make-sound x), si x es una instancia de la clase dog.( defmethod make-sound (( obj dog )) ( formato t "¡guau! ~%" ));; permitir que (make-sound nil) funcione a través de la especialización a una clase nula. ;; Cuerpo vacío inocuo: nada no emite ningún sonido. ( defmethod make-sound (( obj null )))
La clase null
es una subclase de la symbol
clase, porque nil
es un símbolo. Dado que nil
también representa la lista vacía, también null
es una subclase de la list
clase. Los parámetros de los métodos se especializan en un argumento symbol
o list
lo tomarán nil
. Por supuesto, null
aún se puede definir una especialización para la cual es una coincidencia más específica nil
.
Esquema
A diferencia de Common Lisp, y muchos dialectos de Lisp, el dialecto de Scheme no tiene un valor nulo que funciona de esta manera; las funciones car
y cdr
no se pueden aplicar a una lista vacía; Por lo tanto, el código de la aplicación de esquema tiene que usar las funciones de predicado empty?
o pair?
para eludir esta situación, incluso en situaciones en las que Lisp muy similar no necesitaría distinguir los casos vacíos y no vacíos gracias al comportamiento de nil
.
Rubí
En lenguajes de tipo pato como Ruby , la herencia del lenguaje no es necesaria para proporcionar el comportamiento esperado.
class Dog def sound "ladrido" end end class NilAnimal def sound ( * ); fin findef get_animal ( animal = NilAnimal . new ) animal endget_animal ( Perro . nuevo ) . sonido => "ladrar" get_animal . sonido => nulo
Los intentos de parchear directamente NilClass en lugar de proporcionar implementaciones explícitas dan más efectos secundarios inesperados que beneficios.
JavaScript
En lenguajes de tipo pato como JavaScript , la herencia del lenguaje no es necesaria para proporcionar el comportamiento esperado.
clase Perro { sonido () { retorno 'ladrido' ; } }class NullAnimal { sonido () { retorno nulo ; } }función getAnimal ( tipo ) { tipo de retorno === 'perro' ? nuevo Perro () : nuevo NullAnimal (); } [ 'perro' , nulo ]. mapa (( animal ) => obtenerAnimal ( animal ). sonido ()); // Devuelve ["ladrar", nulo]
Java
Interfaz pública Animal { void makeSound () ; }público de clase para perros implementos Animal { público vacío makeSound () { del sistema . fuera . println ( "¡ guau !" ); } }public class NullAnimal implementa Animal { public void makeSound () { // 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 Animal
se requiere una referencia a un objeto, pero no hay un objeto apropiado disponible. Un Animal
objeto nulo es posible ( Animal myAnimal = null;
) y podría ser útil como marcador de posición, pero no puede usarse para llamar a un método. En este ejemplo, myAnimal.makeSound();
arrojará una NullPointerException. Por lo tanto, puede ser necesario código adicional para probar objetos nulos.
El patrón de objeto nulo resuelve este problema proporcionando una NullAnimal
clase especial que se puede instanciar como un objeto de tipo Animal
. Al igual que con C ++ y lenguajes relacionados, esa clase nula especial debe crearse para cada jerarquía de clases que necesite un objeto nulo, ya que a NullAnimal
no sirve de nada cuando lo que se necesita es un objeto nulo que no implementa la Animal
interfaz.
PHP
interfaz Animal { función pública makeSound (); } la clase Perro implementa Animal { función pública makeSound () { echo "Guau .." ; } } class Cat implementa Animal { función pública makeSound () { echo "Meowww .." ; } } class NullAnimal implementa Animal { función pública makeSound () { // silencio ... } } $ animalType = 'elefante' ; cambiar ( $ animalType ) { caso 'perro' : $ animal = nuevo Perro (); romper ; caso 'gato' : $ animal = nuevo Gato (); romper ; predeterminado : $ animal = new NullAnimal (); romper ; } $ animal -> makeSound (); // ..el animal nulo no emite ningún sonido
Visual Basic .NET
La siguiente implementación de patrón de objeto nulo demuestra la clase concreta que proporciona su correspondiente objeto nulo en un campo estático Empty
. Este enfoque se utiliza con frecuencia en el .NET Framework ( String.Empty
, EventArgs.Empty
, Guid.Empty
, etc.).
Public Class Animal Public Shared ReadOnly Empty As Animal = New AnimalEmpty () Pública Overridable Sub MakeSound () de la consola . WriteLine ( "¡ Guau !" ) End Sub End ClassAmigo NotInheritable Clase AnimalEmpty Hereda Animal Public Overrides Sub MakeSound () ' End Sub End Class
Crítica
Este patrón debe usarse con cuidado ya que puede hacer que aparezcan errores / errores como ejecución normal del programa. [6]
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 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 es 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 valor único denominado nulo o nulo. Además, existe una necesidad adicional de probar que ningún código en ningún lugar asigne nulo en lugar del objeto nulo, porque en la mayoría de los casos y en los lenguajes con tipado estático, esto no es un error del compilador si el objeto nulo es de un tipo de referencia, aunque lo haría indudablemente conduce a errores en tiempo de ejecución en partes del código donde se usó 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 manera u otra), verificando el objeto nulo en lugar de la El valor nulo o nulo introduce una sobrecarga, al igual que el patrón singleton probablemente al obtener la referencia singleton.
Ver también
Referencias
- ^ Woolf, Bobby (1998). "Objeto nulo". En Martin, Robert; Riehle, Dirk; Buschmann, Frank (eds.). Lenguajes de patrones de diseño de programas 3 . Addison-Wesley.
- ^ "Trabajar con objetos (Trabajar con nil)" . Biblioteca para desarrolladores de iOS . Apple, Inc. 13 de diciembre de 2012 . Consultado el 19 de mayo de 2014 .
- ^ Fowler, Martin (1999). Refactorización. Mejorando el diseño del código existente . Addison-Wesley. ISBN 0-201-48567-2.
- ^ Kerievsky, Joshua (2004). Refactorización de patrones . Addison-Wesley. ISBN 0-321-21335-1.
- ^ Martin, Robert (2002). Desarrollo de software ágil: principios, patrones y prácticas . Educación Pearson. ISBN 0-13-597444-5.
- ^ Fowler, Martin (1999). Refactorización págs.216
enlaces externos
- Relato de Jeffrey Walker sobre el patrón de objeto nulo
- La descripción de Martin Fowler del caso especial, un patrón un poco más general
- Patrón de objeto nulo revisado
- Introducir la refactorización de objetos nulos
- Tutorial de creación de fuentes
- Patrón de objeto nulo en Swift