Invariantes de clase
Un objeto invariante, o una representación invariante, es un constructo de programación que consiste en una colección de propiedades invariantes que se mantienen intactas sea cual sea el estado del objeto. Esto garantiza que el objeto cumplirá siempre con ciertas condiciones predefinidas, y que los métodos pueden, en consecuencia, hacer referencia al objeto sin el riesgo de hacer presunciones imprecisas.
En programación, concretamente en programación orientada a objetos, las «invariantes de clase» son invariantes usadas para restringir los objetos de una clase. Los métodos de la clase deben respetar la invariante. La invariante de la clase restringe el estado guardado en el objeto.
Las invariantes de la clase se establecen durante su construcción y se mantienen entre las llamadas a métodos públicos. Es posible romper la invariabilidad de la clase entre llamadas a métodos privados, pero no es aconsejable.
Definir invariantes de clase puede ayudar a los programadores y controladores de calidad a localizar más errores de software durante las pruebas de software.
Clases invariantes y herencia
Las ventajas de las invariantes de son más evidentes en presencia de herencia, ya que las invariantes de clase se heredan, es decir, «las invariantes de todas las clases superiores se aplican a la clase actual»[1]
La herencia permite que las clases derivadas alteren la implementación de sus clases base, por lo que una clase derivada podría cambiar el estado de sus instancias de manera incorrecta desde el punto de vista de su clase base. La preocupación por este comportamiento no deseado lleva a algunos diseñadores de software a favorecer la composición sobre la herencia (por ejemplo, «La herencia rompe la encapsulación»).[2]
Sin embargo, debido a la herencia, las invariantes de una clase cualquiera consisten en la suma de todas sus aserciones invariantes con las invariantes heredadas de su clase base. Esto significa que aunque las clases derivadas pueden acceder a los datos de implementación de su clase base, las invariantes de la clase base evitan que las derivadas manipulen estos datos de manera que produzcan una instancia incorrecta en tiempo de ejecución.
Herramientas de programación de soporte a invariantes
Aserciones
Algunos de los lenguajes de programación más comunes, como C++ y Java permiten el uso de aserciones por defecto, estas pueden usarse para definir invariantes de clase.
Un patrón de diseño común para implementar las invariantes de clase en el constructor del objeto es lanzar una excepción si la invariante no se cumple. Dado que los métodos respetan las invariantes, pueden asumir su validez y no necesitan verificarlas explícitamente.
Soporte nativo
Las invariantes de clase son un componente esencial para el patrón de diseño por contrato. Por ello, los lenguajes que proporcionan un soporte completo al diseño por contrato, como Ada, Eiffel o D (lenguaje de programación), también proporcionan un soporte completo a las invariantes de clase.
Soporte no nativo
Java dispone de una herramienta más potente llamada Java Modeling Language que ofrece un método más robusto para definir invariantes de clase.
Ejemplos
D
El lenguaje de programación D dispone de soporte nativo a las invariantes de clase, además de otras técnicas de diseño por contrato.
class Fecha {
int dia;
int hora;
invariant() {
assert(1 <= dia && dia <= 31);
assert(0 <= hora && hora < 24);
}
}
Eiffel
En Eiffel, las invariantes de clase se detalla al final de la clase precedidas por la palabra clave invariant
.
class
FECHA
create
make
feature {NONE} -- Initialization
make (a_dia: INTEGER; a_hora: INTEGER)
-- Inicializa el objeto con `a_dia' y `a_hora'.
require
dia_valido: 1 <= a_dia and a_dia <= 31
hora_valida: 0 <= a_hora and a_hora <= 23
do
dia := a_dia
hora := a_hora
ensure
dia_establecido: dia = a_dia
hora_establecida: hora = a_hora
end
feature -- Acceso
dia: INTEGER
-- dia del mes
hora: INTEGER
-- hora del dia
feature -- Cambio de valores
set_dia (a_dia: INTEGER)
-- Establece `dia' a `a_dia'
require
argumento_valido: 1 <= a_dia and a_dia <= 31
do
dia := a_dia
ensure
dia_establecido: dia = a_dia
end
set_hora (a_hora: INTEGER)
-- Establece `hora' a `a_hora'
require
argumento_valido: 0 <= a_hora and a_hora <= 23
do
hora := a_hora
ensure
hora_establecida: hora = a_hora
end
invariant
dia_valido: 1 <= dia and dia <= 31
hora_valida: 0 <= hora and hora <= 23
end
Java
Este es un ejemplo de invariantes de clase en Java con Java Modeling Language. Las invariantes deben cumplirse después de que finalice el constructor del objeto y al entrar y salir de todos los métodos públicos. Los métodos deben definir precondiciones y postcondiciones para ayudar a cumplir la invariabilidad de la clase.
public class Fecha {
int /*@spec_public@*/ dia;
int /*@spec_public@*/ hora;
/*@invariant 1 <= dia && dia <= 31; @*/ //invariante de clase
/*@invariant 0 <= hora && hora < 24; @*/ //invariante de clase
/*@
@requires 1 <= d && d <= 31;
@requires 0 <= h && h < 24;
@*/
public Fecha(int d, int h) { // constructor
dia = d;
hora = h;
}
/*@
@requires 1 <= d && d <= 31;
@ensures dia == d;
@*/
public void setdia(int d) {
dia = d;
}
/*@
@requires 0 <= h && h < 24;
@ensures hora == h;
@*/
public void sethora(int h) {
hora = h;
}
}
Véase también
Referencias
- Meyer, Bertrand. Object-Oriented Software Construction, segunda edición, Prentice Hall, 1997, p. 570.
- E. Gamma, R. Helm, R. Johnson, y J. Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, Reading, Massachusetts, 1995., p. 20.