
Définition
L'encapsulation est un ensemble d'outils pour contrôler l'accès aux données ou aux méthodes qui gèrent ces données. Une définition détaillée du terme «encapsulation» peut être trouvée dans ma précédente publication sur le Habré à ce lien . Cet article se concentre sur les exemples d'encapsulation en C ++ et C.
Encapsulation en C ++
Par défaut, dans une classe ( class
), les données et les méthodes sont privées ( private
); ils ne peuvent être lus et modifiés que par la classe à laquelle ils appartiennent. Le niveau d'accès peut être modifié à l'aide des mots clés appropriés fournis par C ++.
Plusieurs qualificatifs sont disponibles en C ++ et modifient l'accès aux données comme suit:
- les données publiques sont accessibles à tous;
- protégé (
protected
) - disponible uniquement pour la classe et les classes enfants; - privé -
private
- disponible uniquement pour la classe à laquelle ils appartiennent.
Par souci de concision, seuls deux niveaux (privé et public) seront mis en évidence dans les exemples.
Exemple d'encapsulation
Dans la classe Contact
, les variables et méthodes publiques sont accessibles depuis le programme principal. Les variables et méthodes privées ne peuvent être lues, appelées ou modifiées que par la classe elle-même.
#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; }
Tenter d'imprimer ou de modifier la variable privée mobile_number
partir du programme principal ( main
) provoquera une erreur de compilation car l'accès aux données privées de la classe est limité.
Violation d'encapsulation avec des amis (bonne pratique)
En C ++, il y a le mot-clé «ami» qui vous permet d'ajouter des exceptions aux règles générales d'accès aux données. Si une fonction ou une classe s'appelle un ami de la classe Contact
, ils bénéficient d'un accès gratuit à des données protégées ou privées.
Il y a deux règles de base de l'amitié - l'amitié n'est pas héritée et non mutuelle. De plus, la présence d '«amis» ne modifie pas le niveau de sécurité des données - les données privées restent privées à l'exception des «amis».
#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; }
Dans cet exemple, la fonction print_numbers()
est une fonction normale, pas une méthode de la classe Contact
. Déclarer la fonction print_numbers()
«ami» de la classe Contact
est la seule raison pour laquelle la fonction print_numbers()
a accès à des données privées. Si vous supprimez la ligne avec la définition d'un ami, le code ne sera pas compilé.
Remarque : il est préférable de ne pas abuser d'amis. L'ajout d'un ami doit être considéré comme une exception et non comme une pratique générale.
Violation d'encapsulation avec conversion de type et pointeurs (mauvaise pratique)
Tout d'abord, il convient de noter que l'utilisation de pointeurs et de conversion de type de cette manière est une mauvaise idée. Cette méthode ne garantit pas la réception des données nécessaires. Il est mal lu et mal entretenu. Malgré cela, il existe.
C ++ a hérité de nombreux outils de C, dont l'un est le typecasting
. Par défaut, toutes les variables et méthodes de la classe sont privées. Dans le même temps, le niveau standard d'accès aux données dans la structure ( struct
) est public. Il est possible de créer une structure ou une classe entièrement publique dans laquelle les données seront localisées de manière identique aux données de la classe Contact
et en utilisant la conversion de type pour accéder aux données privées.
#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; }
Les données privées ont été lues et modifiées en raison de la conversion de type
Encapsulation C
L'encapsulation est traditionnellement considérée comme l'un des principes clés de la POO. Cependant, cela ne limite pas l'utilisation de ce principe dans les langues orientées vers la procédure. En C, l'encapsulation est utilisée depuis longtemps, malgré l'absence des mots-clés «privé» et «public».
Variables privées
Dans le cadre de l'encapsulation, toutes les données en C peuvent être considérées comme publiques par défaut. Le niveau d'accès aux variables dans les structures ( struct
) peut être changé en privé si leur définition est isolée du programme principal. L'effet souhaité peut être obtenu en utilisant des fichiers d'en-tête (en-tête, .h) et de source (source, .c) distincts.
Dans cet exemple, la structure a été définie dans un fichier source séparé "private_var.c". Étant donné que l'initialisation de la structure en C nécessite l'allocation et la libération de mémoire, plusieurs fonctions d'assistance ont été ajoutées.
#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); }
Dans le fichier d'en-tête correspondant "private_var.h", la structure Contact
été déclarée, mais son contenu est resté caché au programme principal.
#ifndef PRIVATE_VAR #define PRIVATE_VAR struct Contact; struct Contact * create_contact(); void delete_contact( struct Contact * some_contact ); #endif
Ainsi, pour «main.c», le contenu de la structure est inconnu et les tentatives de lecture ou de modification des données privées entraîneront une erreur de compilation.
#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; }
Accéder aux variables privées avec des pointeurs
La conversion de type peut être utilisée pour surmonter l'encapsulation en C comme en C ++, mais cette approche a déjà été décrite. Sachant que, dans la structure, les données sont organisées dans l'ordre de leur déclaration, les pointeurs et l'arithmétique des pointeurs conviennent pour atteindre l'objectif.
L'accès aux variables de la structure est limité. Cependant, seules les variables sont masquées, pas la mémoire dans laquelle les données sont stockées. Les pointeurs peuvent être considérés comme une référence à l'adresse mémoire, et si cette mémoire est disponible pour le programme, les données stockées dans cette mémoire peuvent être lues et modifiées. Si le pointeur est affecté à la mémoire dans laquelle la structure stocke ses données - elles peuvent être lues. En utilisant la même définition de structure (les mêmes fichiers «.c» et «.h») et le fichier «main.c» modifié, la restriction d'accès a été surmontée.
#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; }
Les données de la structure ont été lues et modifiées
Fonctions privées
Les fonctions, étant externes (externes) par défaut, sont visibles dans toute l' translation unit
dite de translation unit
. En d'autres termes, si plusieurs fichiers sont compilés ensemble en un seul fichier objet, n'importe lequel de ces fichiers pourra accéder à n'importe quelle fonction à partir de n'importe quel autre fichier. L'utilisation du static
- static
"statique" lors de la création d'une fonction limitera sa visibilité au fichier dans lequel elle a été définie. Par conséquent, pour garantir la confidentialité de la fonction, vous devez effectuer plusieurs étapes:
- la fonction doit être déclarée statique (
static
) soit dans le fichier source (.c) soit dans le fichier d'en-tête correspondant (.h); - la définition de la fonction doit être dans un fichier source séparé.
Dans cet exemple, la fonction statique print_numbers()
été définie dans le fichier «private_funct.c». Par ailleurs, la fonction delete_contact()
appelle avec succès print_numbers()
car ils sont dans le même fichier.
#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); }
Dans le fichier d'en-tête correspondant "private_funct.h", print_numbers()
été déclaré comme une fonction statique.
#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
Le programme principal, «main.c», appelle avec succès print_numbers()
indirectement via delete_contact()
, car les deux fonctions sont dans le même document. Cependant, une tentative d'appeler print_numbers()
partir du programme principal print_numbers()
erreur.
#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; }
Accès aux fonctionnalités privées
Il est print_numbers()
appeler print_numbers()
depuis le programme principal. Pour ce faire, vous pouvez utiliser le mot-clé goto
ou passer un pointeur vers une fonction privée dans main
. Les deux méthodes nécessitent des modifications soit dans le fichier source «private_funct.c», soit directement dans le corps de la fonction elle-même. Étant donné que ces méthodes ne contournent pas l'encapsulation et l'annulent, elles dépassent le cadre de cet article.
Conclusion
L'encapsulation existe en dehors des langages POO. Les langages POO modernes rendent l'utilisation de l'encapsulation pratique et naturelle. Il existe de nombreuses façons de contourner l'encapsulation et éviter les pratiques douteuses aidera à la préserver en C et C ++.