Invariante de clase
En programación informática, específicamente en programación orientada a objetos, un invariante de clase (o invariante de tipo) es un invariante que se utiliza para restringir objetos de una clase. Los métodos de la clase deben preservar el invariante. La invariante de clase restringe el estado almacenado en el objeto.
Los invariantes de clase se establecen durante la construcción y se mantienen constantemente entre llamadas a métodos públicos. El código dentro de las funciones puede romper invariantes siempre que los invariantes se restablezcan antes de que finalice una función pública. Con la concurrencia, mantener la invariante en los métodos generalmente requiere que se establezca una sección crítica bloqueando el estado mediante un mutex.
Un invariante de objeto, o invariante de representación, es una construcción de programación informática que consta de un conjunto de propiedades invariantes que permanecen intactas independientemente del estado del objeto. Esto garantiza que el objeto siempre cumplirá condiciones predefinidas y que, por lo tanto, los métodos siempre podrán hacer referencia al objeto sin riesgo de hacer suposiciones inexactas. Definir invariantes de clase puede ayudar a los programadores y evaluadores a detectar más errores durante las pruebas de software.
Invariantes de clase y herencia
El efecto útil de los invariantes de clase en software orientado a objetos se mejora en la presencia de la herencia. Los invariantes de clase son heredados, es decir, "los invariantes de todos los padres de una clase se aplican a la clase misma."
La herencia puede permitir que las clases descendientes alteren los datos de implementación de las clases principales, por lo que sería posible que una clase descendiente cambiara el estado de las instancias de una manera que las hiciera inválidas desde el punto de vista de la clase principal. La preocupación por este tipo de descendiente que se porta mal es una de las razones que dan los diseñadores de software orientado a objetos para favorecer la composición sobre la herencia (es decir, la herencia rompe la encapsulación).
Sin embargo, debido a que las invariantes de clase se heredan, las invariantes de clase para cualquier clase en particular consisten en cualquier aserción invariante codificada inmediatamente en esa clase junto con todas las cláusulas invariantes heredadas de los padres de la clase. Esto significa que aunque las clases descendientes puedan tener acceso a los datos de implementación de sus padres, la clase invariante puede evitar que manipulen esos datos de cualquier manera que produzca una instancia no válida en tiempo de ejecución.
Soporte de lenguajes de programación
Asserciones
Los lenguajes de programación comunes como Python, PHP, JavaScript, C++ y Java admiten aserciones de forma predeterminada, que se pueden usar para definir invariantes de clase. Un patrón común para implementar invariantes en clases es que el constructor de la clase lance una excepción si el invariante no se cumple. Dado que los métodos preservan las invariantes, pueden asumir la validez de las invariantes y no necesitan verificarla explícitamente.
Apoyo nativo
La invariante de clase es un componente esencial del diseño por contrato. Por lo tanto, los lenguajes de programación que brindan soporte nativo completo para el diseño por contrato, como Eiffel, Ada y D, también brindarán soporte completo para las invariantes de clase.
Soporte no nativo
Para C++, la biblioteca Loki proporciona un marco para verificar invariantes de clase, invariantes de datos estáticos y seguridad de excepciones.
Para Java, existe una herramienta más poderosa llamada Java Modeling Language que proporciona una forma más sólida de definir invariantes de clase.
Ejemplos
Soporte nativo
Ada
El lenguaje de programación Ada tiene soporte nativo para invariantes de tipo (así como condiciones previas y posteriores, predicados de subtipo, etc.). Se puede proporcionar una invariante de tipo en un tipo privado (por ejemplo, para definir una relación entre sus propiedades abstractas) o en su definición completa (normalmente para ayudar a verificar la corrección de la implementación del tipo). A continuación se muestra un ejemplo de un tipo invariante proporcionado en la definición completa de un tipo privado utilizado para representar una pila lógica. La implementación utiliza una matriz y el tipo invariante especifica ciertas propiedades de la implementación que permiten pruebas de seguridad. En este caso, el invariante garantiza que, para una pila de profundidad lógica N, los primeros N elementos de la matriz sean valores válidos. La Default_Initial_Condition del tipo Stack, al especificar una pila vacía, garantiza la verdad inicial del invariante y Push preserva el invariante. La verdad del invariante le permite a Pop confiar en el hecho de que la parte superior de la pila es un valor válido, lo cual es necesario para probar la poscondición de Pop. Un invariante de tipo más complejo permitiría probar la corrección funcional completa, como que Pop devuelve el valor pasado en un Push correspondiente, pero en este caso simplemente estamos tratando de demostrar que Pop no devuelve un Invalid_Value.
genérico genérico Tipo Tema es privado; Invalid_Value : dentro Tema;paquete Stacks es Tipo Stack()Max_Depth : Positivo) es privado con Default_Initial_Condition = Is_Empty ()Stack); función Is_Empty()S : dentro Stack) retorno Boolean; función Is_Full()S : dentro Stack) retorno Boolean; procedimiento Empuja()S : dentro Fuera. Stack; I : dentro Tema) con Pre = no Is_Full()S) y luego I /= Invalid_Value, Puesto = no Is_Empty()S); procedimiento Pop()S : dentro Fuera. Stack; I : Fuera. Tema) con Pre = no Is_Empty()S), Puesto = no Is_Full()S) y luego I /= Invalid_Value;privado Tipo Item_Array es array ()Positivo rango ■ de Tema; Tipo Stack()Max_Depth : Positivo) es récord Duración : Natural := 0; Datos : Item_Array ()1 .. Max_Depth) := ()otros = Invalid_Value); final record con Tipo_Invariante = Duración . Max_Depth y entonces ()para Todos J dentro 1 .. Duración = Datos ()J) /= Invalid_Value); función Is_Empty()S : dentro Stack) retorno Boolean es ()S.Duración = 0); función Is_Full()S : dentro Stack) retorno Boolean es ()S.Duración = S.Max_Depth);final Stacks;
D
El lenguaje de programación D tiene soporte nativo de invariantes de clase, así como otras técnicas de programación de contratos. Aquí hay un ejemplo de la documentación oficial.
clase Fecha {} int día; int hora; invariable() {} afirmación()día >= 1 " día . 31); afirmación()hora >= 0 " hora . 23); }}
Eiffel
En Eiffel, la clase invariante aparece al final de la clase después de la palabra clave invariante
.
claseFechacrearhacerfunción {}NINGUNA} - Iniciaciónhacer ()A_day: INTEGER; a horas: INTEGER)- Iniciar `Current' con `a_day' y `a_hour'.necesidadvalid_day: A_day >= 1 y A_day . 31valid_hora: a horas >= 0 y a horas . 23dodía := A_dayhora := a horasasegurarday_set: día = A_dayhour_set: hora = a horasfinalfunción - Accesodía: INTEGER- Día del mes para el `Current'hora: INTEGER- Hora del día para 'Current'función - Cambio de elementoset_day ()A_day: INTEGER)-- Set `day' to `a_day 'necesidadvalid_argument: A_day >= 1 y A_day . 31dodía := A_dayasegurarday_set: día = A_dayfinalset_hour ()a horas: INTEGER)-- Set `hour' to `a_hour 'necesidadvalid_argument: a horas >= 0 y a horas . 23dohora := a horasasegurarhour_set: hora = a horasfinalinvariablevalid_day: día >= 1 y día . 31valid_hora: hora >= 0 y hora . 23final
Soporte no nativo
C++
La biblioteca Loki (C++) proporciona un marco escrito por Richard Sposato para verificar invariantes de clase, invariantes de datos estáticos y niveles de seguridad de excepción.
Este es un ejemplo de cómo la clase puede usar Loki::Checker para verificar que las invariantes sigan siendo verdaderas después de que un objeto cambia. El ejemplo utiliza un objeto de geopunto para almacenar una ubicación en la Tierra como una coordenada de latitud y longitud.
Las invariantes de geopunto son:
- La latitud puede no ser más de 90° norte.
- La latitud puede no ser inferior a -90° al sur.
- longitud puede no ser más de 180° este.
- longitud puede no ser inferior a -180° oeste.
#include ■loki/Checker.h // Necesitaba chequear invariantes de clase.#include ■Degrees.hppclase GeoPoint {} público: GeoPoint()Grados latitud, Grados longitud); // Mover la función moverá la ubicación de GeoPoint. vacío Muévanse.()Grados latitud_change, Grados longitude_change) {} // El objeto de control llama a IsValid en la entrada de la función y salida para probar esto // El objeto GeoPoint es válido. El comprobador también garantiza GeoPoint::Move // función nunca lanzará. CheckFor::CheckForNoThrow checker()esto, "IsValid); latitud_ += latitud_change; si ()latitud_ >= 90.0) latitud_ = 90.0; si ()latitud_ . -90.0) latitud_ = -90.0; longitude_ += longitude_change; mientras ()longitude_ >= 180.0) longitude_ -= 360.0; mientras ()longitude_ . -180.0) longitude_ += 360.0; } privado: /** @note Check Para realizar comprobaciones de validez en muchas funciones para determinar si el código viola cualquier invariante, si algún contenido cambia, o si el la función lanzó una excepción. */ utilizando CheckFor = ::Loki::CheckFor.const GeoPoint■; // Esta función comprueba todos los invariantes de objetos. bool IsValid() const {} afirmación()esto ! nullptr); afirmación()latitud_ >= -90.0); afirmación()latitud_ . 90.0); afirmación()longitude_ >= -180.0); afirmación()longitude_ . 180.0); retorno verdadero; } Grados latitud_; ///Se realizaron Grados del Ecuador. Positivo es norte, negativo es /// se realizó al sur. Grados longitude_; ///Se realizaron Grados del Primer Meridiano. Positivo es este, /// negativo es el oeste.}
Java
Este es un ejemplo de una clase invariante en el lenguaje de programación Java con Java Modeling Language. El invariante debe ser verdadero una vez finalizado el constructor y en la entrada y salida de todos los miembros públicos. funciones. Las funciones miembro públicas deben definir condiciones previas y posteriores para ayudar a garantizar la invariancia de la clase.
público clase Fecha {} int /*@spec_public@*/ día; int /*@spec_public@*/ hora; /*@día invariable 1 " punto " = 31; @*/ //clase invariable /*@hora invariable 0 " hora " = 23; @*/ //clase invariable /*@ @requires d mento= 1 " d " = 31; @requires h ≤= 0 " cosecha h " = 23; @*/ público Fecha()int d, int h) {} // constructor día = d; hora = h; } /*@ @requires d mento= 1 " d " = 31; @ensures day == d; @*/ público vacío setDay()int d) {} día = d; } /*@ @requires h ≤= 0 " cosecha h " = 23; @ensures hour == h; @*/ público vacío setHour()int h) {} hora = h; }}
Contenido relacionado
Tarjeta perforada
CPython
Arquitectura Harvard