
Definición
La encapsulación es un conjunto de herramientas para controlar el acceso a datos o métodos que administran esos datos. Puede encontrar una definición detallada del término "encapsulación" en mi publicación anterior en el Habré en este enlace . Este artículo se centra en ejemplos de encapsulación en C ++ y C.
Encapsulación en C ++
Por defecto, en una clase ( class
) los datos y métodos son privados ( private
); solo pueden ser leídos y modificados por la clase a la que pertenecen. El nivel de acceso se puede cambiar utilizando las palabras clave apropiadas que proporciona C ++.
Hay varios calificadores disponibles en C ++ y modifican el acceso a los datos de la siguiente manera:
- los datos públicos están disponibles para todos;
- protegido (
protected
): disponible solo para la clase y las clases secundarias; - privado -
private
- disponible solo para la clase a la que pertenecen.
Por brevedad, solo dos niveles (privado y público) se resaltarán en los ejemplos.
Ejemplo de encapsulación
En la clase Contact
, las variables públicas y los métodos son accesibles desde el programa principal. Las variables y métodos privados solo pueden ser leídos, llamados o cambiados por la clase misma.
#include <iostream> using namespace std; class Contact { private: int mobile_number; // private variable int home_number; // private variable public: Contact() // constructor { mobile_number = 12345678; home_number = 87654321; } void print_numbers() { cout << "Mobile number: " << mobile_number; cout << ", home number: " << home_number << endl; } }; int main() { Contact Tony; Tony.print_numbers(); // cout << Tony.mobile_number << endl; // will cause compile time error return 0; }
Intentar imprimir o modificar la variable privada mobile_number
desde el programa principal ( main
) causará un error de compilación porque el acceso a los datos privados en la clase es limitado.
Violación de encapsulación con amigos (buena práctica)
En C ++, existe la palabra clave "amigo" que le permite agregar excepciones a las reglas generales para acceder a los datos. Si una función o clase se llama amiga de la clase Contact
, obtienen acceso gratuito a datos protegidos o privados.
Hay dos reglas básicas de amistad: la amistad no se hereda y no es mutua. Además, la presencia de "amigos" no cambia el nivel de seguridad de los datos: los datos privados siguen siendo privados con la excepción de "amigos".
#include <iostream> using namespace std; class Contact { private: int mobile_number; // private variable int home_number; // private variable public: Contact() // constructor { mobile_number = 12345678; home_number = 87654321; } // Declaring a global 'friend' function friend void print_numbers( Contact some_contact ); }; void print_numbers( Contact some_contact ) { cout << "Mobile number: " << some_contact.mobile_number; cout << ", home number: " << some_contact.home_number << endl; } int main() { Contact Tony; print_numbers(Tony); return 0; }
En este ejemplo, la función print_numbers()
es una función normal, no un método de la clase Contact
. Declarar la función print_numbers()
"amigo" de la clase Contact
es la única razón por la cual la función print_numbers()
tiene acceso a datos privados. Si elimina la línea con la definición de un amigo, el código no se compilará.
Nota : es mejor no abusar de los amigos. Agregar un amigo debe considerarse como una excepción, no como una práctica general.
Infracción de encapsulación con conversión de tipo y punteros (mala práctica)
En primer lugar, vale la pena señalar que usar punteros y conversión de tipos de esta manera es una mala idea. Este método no garantiza la recepción de los datos necesarios. Está mal leído y mal mantenido. A pesar de esto, él existe.
C ++ heredó muchas herramientas de C, una de las cuales es la typecasting
. Por defecto, todas las variables y métodos en la clase son privados. Al mismo tiempo, el nivel estándar de acceso a datos en la estructura ( struct
) es público. Es posible crear una estructura o una clase totalmente pública en la que los datos se ubicarán de manera idéntica a los datos en la clase Contact
y utilizando la conversión de tipos para acceder a datos privados.
#include <iostream> using namespace std; class Contact { private: int mobile_number; // private variable int home_number; // private variable public: Contact() // constructor { mobile_number = 12345678; home_number = 87654321; } void print_numbers() { cout << "Mobile number: " << mobile_number; cout << ", home number: " << home_number << endl; } }; struct Contact_struct { int mobile_number; int home_number; }; int main() { Contact Tony; Contact_struct * structured_Tony; Tony.print_numbers(); structured_Tony = (Contact_struct *) & Tony; structured_Tony->mobile_number = 20; structured_Tony->home_number = 30; Tony.print_numbers(); return 0; }
Los datos privados se han leído y modificado debido a la conversión de tipos
Encapsulación C
La encapsulación se considera tradicionalmente como uno de los principios clave de OOP. Sin embargo, esto no limita el uso de este principio en lenguajes orientados a procedimientos. En C, la encapsulación se ha utilizado durante mucho tiempo, a pesar de la falta de las palabras clave "privado" y "público".
Variables privadas
En el contexto de la encapsulación, todos los datos en C pueden considerarse públicos por defecto. El nivel de acceso a las variables en las estructuras ( struct
) puede cambiarse a privado si su definición está aislada del programa principal. El efecto deseado se puede lograr mediante el uso de archivos separados de encabezado (encabezado, .h) y fuente (fuente, .c).
En este ejemplo, la estructura se definió en un archivo fuente separado "private_var.c". Como inicializar la estructura en C requiere asignar y liberar memoria, se han agregado varias funciones auxiliares.
#include "private_var.h" #include <stdio.h> #include <stdlib.h> struct Contact { int mobile_number; int home_number; }; struct Contact * create_contact() { struct Contact * some_contact; some_contact = malloc(sizeof(struct Contact)); some_contact->mobile_number = 12345678; some_contact->home_number = 87654321; return( some_contact ); } void delete_contact( struct Contact * some_contact ) { free(some_contact); }
En el archivo de encabezado correspondiente "private_var.h", se declaró la estructura de Contact
, pero su contenido permaneció oculto para el programa principal.
#ifndef PRIVATE_VAR #define PRIVATE_VAR struct Contact; struct Contact * create_contact(); void delete_contact( struct Contact * some_contact ); #endif
Por lo tanto, para "main.c" se desconoce el contenido de la estructura y los intentos de leer o modificar datos privados provocarán un error de compilación.
#include "private_var.h" #include <stdio.h> int main() { struct Contact * Tony; Tony = create_contact(); // printf( "Mobile number: %d\n", Tony->mobile_number); // will cause compile time error delete_contact( Tony ); return 0; }
Acceso a variables privadas con punteros
La conversión de tipos se puede utilizar para superar la encapsulación en C, así como en C ++, pero este enfoque ya se ha descrito. Sabiendo que en la estructura los datos están organizados en el orden de su declaración, los punteros y la aritmética de los punteros son adecuados para lograr el objetivo.
El acceso a las variables en la estructura es limitado. Sin embargo, solo las variables están ocultas, no la memoria en la que se almacenan los datos. Los punteros se pueden considerar como una referencia a la dirección de memoria, y si esta memoria está disponible para el programa, los datos almacenados en esta memoria se pueden leer y cambiar. Si el puntero se asigna a la memoria en la que la estructura almacena sus datos, se pueden leer. Utilizando la misma definición de estructura (los mismos archivos ".c" y ".h") y el archivo "main.c" modificado, se superó la restricción de acceso.
#include "private_var.h" #include <stdio.h> int main() { struct Contact * Tony; Tony = create_contact(); int * mobile_number_is_here = (int *)Tony; printf("Mobile number: %d\n", *mobile_number_is_here); int * home_number_is_here = mobile_number_is_here + 1; *home_number_is_here = 1; printf("Modified home number: %d\n", *home_number_is_here); delete_contact( Tony ); return 0; }
Los datos en la estructura han sido leídos y modificados
Funciones privadas
Las funciones, siendo externas ( extern
) por defecto, son visibles en toda la llamada translation unit
. En otras palabras, si varios archivos se compilan juntos en un archivo de objeto, cualquiera de estos archivos podrá acceder a cualquier función desde cualquier otro archivo. El uso de la static
"estática" al crear una función limitará su visibilidad al archivo en el que se definió. Por lo tanto, para garantizar la privacidad de la función, debe realizar varios pasos:
- la función debe declararse estática (
static
) en el archivo fuente (.c) o en el archivo de encabezado correspondiente (.h); - la definición de la función debe estar en un archivo fuente separado.
En este ejemplo, la función estática print_numbers()
se definió en el archivo "private_funct.c". Por cierto, la función delete_contact()
llama con éxito a print_numbers()
ya que están en el mismo archivo.
#include "private_funct.h" #include <stdio.h> #include <stdlib.h> struct Contact { int mobile_number; int home_number; }; struct Contact * create_contact() { struct Contact * some_contact; some_contact = malloc(sizeof(struct Contact)); some_contact->mobile_number = 12345678; some_contact->home_number = 87654321; return( some_contact ); } static void print_numbers( struct Contact * some_contact ) { printf("Mobile number: %d, ", some_contact->mobile_number); printf("home number = %d\n", some_contact->home_number); } void delete_contact( struct Contact * some_contact ) { print_numbers(some_contact); free(some_contact); }
En el archivo de encabezado correspondiente " print_numbers()
", print_numbers()
sido declarado como una función estática.
#ifndef PRIVATE_FUNCT_H #define PRIVATE_FUNCT_H struct Contact; struct Contact * create_contact(); static void print_numbers( struct Contact * some_contact ); void delete_contact( struct Contact * my_points ); #endif
El programa principal, "main.c", llama con éxito a print_numbers()
indirectamente a través de delete_contact()
, ya que ambas funciones están en el mismo documento. Sin embargo, un intento de llamar a print_numbers()
desde el programa principal print_numbers()
error.
#include "private_funct.h" #include <stdio.h> int main() { struct Contact * Tony; Tony = create_contact(); // print_numbers( Tony ); // will cause compile time error delete_contact( Tony ); return 0; }
Acceso a funciones privadas
Es print_numbers()
llamar a print_numbers()
desde el programa principal. Para hacer esto, puede usar la palabra clave goto
o pasar un puntero a una función privada en main
. Ambos métodos requieren cambios en el archivo fuente "private_funct.c" o directamente en el cuerpo de la función en sí. Dado que estos métodos no omiten la encapsulación y la cancelan, están más allá del alcance de este artículo.
Conclusión
La encapsulación existe fuera de los lenguajes OOP. Los lenguajes OOP modernos hacen que el uso de la encapsulación sea conveniente y natural. Hay muchas formas de eludir la encapsulación y evitar prácticas cuestionables ayudará a preservarla tanto en C como en C ++.