En todos los tratados de programación orientada a objetos se hace mención al problema que supone escribir funciones que devuelven objetos con valores null, ya que después de llamarlas y antes de poder usar el valor devuelto hay que comprobar si tienen el valor [null] para hacer una cosa u otra y evitar el ya comentado error [NullReferenceException].
En los lenguajes orientados a objetos es normal que un objeto instanciado tome el valor null. También es frecuente el error que se produce cuando se intenta usar uno de estos objetos, el error NullReferenceException.
Por otra parte, en todos los tratados de programación orientada a objetos se hace mención al problema que supone escribir funciones que devuelven objetos con valores null, ya que después de llamarlas y antes de poder usar el valor devuelto hay que comprobar si tienen el valor [null] para hacer una cosa u otra y evitar el ya comentado error [NullReferenceException].
La solución más evidente a este problema es que las funciones en lugar de devolver un valor [null] lancen una excepción. Esta forma de actuar simplifica el código, pero sigue obligándonos a comprobar la presencia de excepciones
Una solución más elegante es el uso de objetos especiales, que consiste en devolver un objeto especial que cumple con la interfaz esperada pero que sus métodos están vacios y no hace nada
Por ejemplo, Supongamos una función que explora los directorios y devuelve por cada directorio una lista con los ficheros que contiene, para realizar alguna acción (que no nos interesa describir) en cada uno de los ficheros.
Cuando aparece un directorio vacio (sin ficheros), ya sabemos que podemos optar por devolver el valor Null, devolver una excepción, o devolver un objeto especial, una lista (que es lo que se espera recibir) pero vacía.
La solución más correcta e s la tercera, Utilizar algún patrón como Null Object pattern o una versión más general (Special Case) propuesta por Martin Fowler.
La solución que propone este patrón es devolver un Objeto nulo. La ventaja de devolver un objeto nulo (es decir, una lista vacía) en lugar del valor Null es que no hay que verificar el valor de retorno porque de hecho es una lista
La función de llamada siempre recibe un objeto lista (que es lo que espera) y puede iterar la lista de forma normal, como recibe una lista no se genera ningún error, y como está vacía, pues no hace nada con la lista, por lo que el código trata ese caso especial de forma trasparente.
Sin embargo, es posible comprobar si el valor de retorno es un objeto nulo (por ejemplo, una lista vacía) y reaccionar de manera diferente si se desea.
La clave del patrón Null Object es una clase abstracta que define la interfaz para todos los objetos de este tipo. El patrón se implementa como una clase derivada de dicha clase abstracta. Por lo que puede ser usada en cualquier lugar que este tipo de objeto sea necesario.
La clase Objeto Null, tiene la misma interfaz que una clase "Real" y "sabe" que hacer en cada uno de sus métodos. La alternativa a usar este patrón, es usar el valor "null" que, evidentemente, no implementa la interfaz abstracta y que ya hemos comentado que debe comprobarse su valor por código específico en todos los objetos que empleemos.
Se puede considerar como un caso especial de State pattern and the Strategy pattern.
No es un patrón de patrones de diseño , pero se menciona en Martin Fowler Refactoring [2] y Josué Kerievsky de [3] libro sobre refactorización en el Insertar objeto nulo refactorización .
/* Null Object Pattern implementation:*/ using System; // Interfaz de los animales es la clave para la compatibilidad // para las implementaciones de los animales inferiores. interface IAnimal { void Sonido(); } // Perro es un animal real class Perro : IAnimal { public void Sonido() { Console.WriteLine("Guau!"); } } // El caso nulo: esta clase NullAnimal deberá ser instanciada // y utilizarse en lugar de la palabra clave null. class NullAnimal : IAnimal { public void Sonido() { // no se especifica ( a proposito) // ningún comportamiento.. } } /* ========================= * Ejemplo de uso simple de entrada principal. */ static class Program { static void Main() { IAnimal dog = new Perro(); dog.Sonido(); // Salida "Guau!" /* En lugar de utilizar C # Null, utilice una instancia NullAnimal. * Este ejemplo es simplista pero transmite la idea de que si * una instancia de NullAnimal se utiliza entonces el programa * Nunca va a experimentar una System.NullReferenceException. NET * en tiempo de ejecución. */ IAnimal desconocido = new NullAnimal(); //<< Reemplaza: IAnimal unknown = null; desconocido.Sonido(); // Ninguna salida, pero no se producirá una excepción en tiempo de ejecución } }
Supongamos una clase a la que llamaremos [AccesoDatosPAD] y su trabajo consiste en la puesta al día de un pequeño fichero de acceso aleatorio por índice. Para simplificar la tarea el fichero tiene una capacidad previamente predefinida (por ejemplo 50 registros)
Existe una clase [Registro] que contiene un registro que se grabara / leerá en el fichero este registro debe tener el tamaño fijo, por lo que los campos de texto se rellenan de blancos hasta alcanzar el tamaño que debe tener ese campo.
Como todos los registros tienen el mismo tamaño, para acceder al registro que ocupa, por ejemplo, la quinta posición, lo que tengo que hacer es situarme al principio del fichero, y después saltar el número de bytes que corresponden a cada registro multiplicado por uno menos que la posición de acceso (en este ejemplo por cuatro) y a continuación leer un registro.
El registro que se lee puede contener datos o estar vacio, la función que lee el registro será más o menos algo parecido a esto:
private IRegistro ReadRegistro(System.IO.BinaryReader br) { /* ----------------------------------------------------- * He aplicado el patrón Caso Especial * Consiste en diseñar un objeto para un caso especial * que se use mucho, en este caso un registro vacio * Esta función usa ese patrón y cuando lee el fichero * devuelve un objeto Registro o un Objeto RegistroEmpty * En realidad y en aplicación del patrón devuelve un objeto interface. * --------------------------------------------------- */ // Instanciar un registro normal para leer los datos Registro nuevoRegistro = new Registro(); // Proceso de lectura de los datos del registro en el fichero nuevoRegistro.Borrado = br.ReadBoolean(); nuevoRegistro.Nombre = br.ReadString(); nuevoRegistro.Edad = br.ReadInt32(); nuevoRegistro.Numero = br.ReadInt64(); // Elegir el tipo de objeto que se devuelve, // Uno especial o uno normal if (nuevoRegistro.IsEmpty == true) { return RegistroEmpty.Instance; } else { return nuevoRegistro; } }
En un programa que emplea un lenguaje de programación orientado a objetos estos están constantemente relacionándose entre sí a través de llamadas a métodos y a través de las referencias que un objeto posee de otros. Sin embargo, es habitual que un determinado método devuelva un null en vez de una referencia a un objeto. Esta referencia null puede ser un problema ya que nos obliga en el código hacer una comprobación antes de poder llamarlo. Si un método devuelve un null puede dar como resultado un [NullReferenceException] en otra parte de la aplicación en donde se intente usar esa referencia y no se haga la comprobación.
Para tratar de evitar llenar nuestro código java de sentencias if con la comprobación de null podemos utilizar el patrón de diseño [Null Object Pattern]. La idea de este patrón es que en vez de devolver un null como resultado de la llamada a un método devolvamos un objeto que no haga nada en las llamadas a los métodos en los que se use. Por ejemplo, supongamos que tenemos un método que en base a un enum se encarga de devolver un objeto que sigue el patrón Command. Y ahora supongamos que para cierto valor del enum no hay objeto command que se pueda devolver, podríamos devolver null en cuyo caso nos veríamos obligados a realizar la comprobación por null o empleando la idea del patrón [Null Object Pattern] devolver un objeto que implemente la interfaz command en cuestión pero que no haga nada o haga una operación inocua. Si vemos que en un programa estamos llenándolo de sentencias if (objeto == null) tal vez podamos aplicar este patrón. Lo importante para poder eliminar esos if es determinar que es una operación inocua, si se trata de un objeto puede ser que el método no haga nada, si se trata de un número que se utiliza para sumar o multiplicar se puede devolver 0 o 1 respectivamente en vez de null, depende del caso y la operación a simular.
Esta patrón puede usarse también para evitar la excepción [NullReferenceException] pero no es tanto la misión del patrón la misión como evitar preocuparnos por si las referencias son null o no y eliminar ifs, es cierto que empleándolo no dará la excepción pero si la aplicación continua puede producir otra excepción o un comportamiento no deseado más complicado de resolver y de averiguar su causa en otra parte del código.
Veámoslo con el ejemplo de una factoría que para determinados enumerados se devuelve un objeto que sigue el patrón command pero para ciertos valores del enumerado no hay command válido y en vez de devolver null devolvemos un command [Null Object Pattern] este es el caso de llamar a la factoría con un enumerado null. Para el enumerado [Operacion. Mensaje] se devuelve un command que emite un mensaje, para [Operacion. No_Mensaje ] y [null] se devuelve un command que no hace nada.
Module Module1 Sub Main() Dim objTest As New TestOperacionCommandFactory End Sub Public Class OperacionCommandFactory Public Enum Operacion None Mensaje No_Mensaje End Enum Public Function buildCommand(operacion As Operacion) As OperacionCommand If operacion = Nothing Then Return New NoOperacionCommand() End If Select Case operacion Case OperacionCommandFactory.Operacion.Mensaje Return New MensajeCommand() Case OperacionCommandFactory.Operacion.None Return New NoOperacionCommand() Case OperacionCommandFactory.Operacion.No_Mensaje Return New NoOperacionCommand() Case Else Throw New System.ComponentModel.InvalidEnumArgumentException("Opcion no valida", operacion, GetType(Operacion)) End Select End Function End Class Public Interface OperacionCommand Sub Operacion() End Interface Public Class NoOperacionCommand Implements OperacionCommand Public Sub Operacion() Implements OperacionCommand.Operacion Console.WriteLine("Esta linea no debe escribirse, solo lo hago para saber que funciona") End Sub End Class Public Class MensajeCommand Implements OperacionCommand Public Sub Operacion() Implements OperacionCommand.Operacion Console.WriteLine("Hola mundo!") End Sub End Class Public Class TestOperacionCommandFactory Private factory As OperacionCommandFactory = Nothing Public Sub New() Console.WriteLine("Empieza el test") factory = New OperacionCommandFactory() Call test1() Call test2() Call test3() Console.WriteLine("/Eof el test") Console.ReadKey() End Sub Public Sub test1() Dim operacion As OperacionCommand = factory.buildCommand(OperacionCommandFactory.Operacion.Mensaje) operacion.Operacion() End Sub Public Sub test2() Dim operacion As OperacionCommand = factory.buildCommand(OperacionCommandFactory.Operacion.No_Mensaje) operacion.Operacion() End Sub Public Sub test3() Dim operacion As OperacionCommand = factory.buildCommand(OperacionCommandFactory.Operacion.None) operacion.Operacion() End Sub Public Sub test4() Dim operacion As OperacionCommand = factory.buildCommand(Nothing) operacion.Operacion() End Sub End Class End Module