Tout d'abord, soyons clairs sur le paradigme.
- Structures de données -> une disposition de la mémoire qui peut être parcourue et manipulée par des fonctions bien informées.
- Objets -> un module autonome qui cache son implémentation et fournit une interface qui peut être communiquée via.
Où un getter / setter est-il utile?
Les getters / setters sont-ils utiles dans les structures de données? Non.
Une structure de données est une spécification de disposition de mémoire qui est commune et manipulée par une famille de fonctions.
Généralement, toute ancienne fonction nouvelle peut venir et manipuler une structure de données, si elle le fait de manière à ce que les autres fonctions puissent toujours la comprendre, alors la fonction rejoint la famille. Sinon, c'est une fonction escroc et une source de bugs.
Ne vous méprenez pas, il pourrait y avoir plusieurs familles de fonctions qui se disputent cette structure de données avec des snitches, des turn-coats et des agents doubles partout. C'est bien quand ils ont chacun leur propre structure de données pour jouer, mais quand ils la partagent ... imaginez simplement plusieurs familles criminelles en désaccord sur la politique, cela peut devenir un gâchis très rapidement.
Étant donné le désordre que les familles de fonctions étendues peuvent atteindre, existe-t-il un moyen de coder la structure des données afin que les fonctions non autorisées ne gâchent pas tout? Oui, ils sont appelés objets.
Les getters / setters sont-ils utiles dans les objets? Non.
L'intérêt de regrouper une structure de données dans un objet était de garantir qu'aucune fonction non autorisée ne pouvait exister. Si la fonction voulait rejoindre la famille, elle devait d'abord être soigneusement examinée, puis faire partie de l'objet.
Le but / but d'un getter et d'un setter est de permettre à des fonctions en dehors de l'objet de modifier directement la disposition de la mémoire de l'objet. Cela ressemble à une porte ouverte pour permettre aux voyous ...
The Edge Case
Il y a deux situations où un getter / setter public avait du sens.
- Une partie de la structure de données à l'intérieur de l'objet est gérée par l'objet, mais n'est pas contrôlée par l'objet.
- Une interface décrivant une abstraction de haut niveau d'une structure de données où certains éléments ne devraient pas être en contrôle de l'objet d'implémentation.
Les conteneurs et les interfaces de conteneurs sont des exemples parfaits de ces deux situations. Le conteneur gère les structures de données (liste liée, carte, arborescence) en interne, mais donne le contrôle de l'élément spécifique à tout le monde. L'interface résume cela et ignore complètement l'implémentation et décrit uniquement les attentes.
Malheureusement, de nombreuses implémentations se trompent et définissent l'interface de ces sortes d'objets pour donner un accès direct à l'objet réel. Quelque chose comme:
interface Container<T>
{
typedef ...T... TRef; //<somehow make TRef to be a reference or pointer to the memory location of T
TRef item(int index);
}
C'est cassé. Les implémentations de Container doivent explicitement remettre le contrôle de leurs composants internes à quiconque les utilise. Je n'ai pas encore vu un langage à valeur mutable où cela soit bien (les langages avec une sémantique à valeur immuable sont par définition bien du point de vue de la corruption des données, mais pas nécessairement du point de vue de l'espionnage des données).
Vous pouvez améliorer / corriger les getters / setter en utilisant uniquement la copie sémantique, ou en utilisant un proxy:
interface Proxy<T>
{
operator T(); //<returns a copy
... operator ->(); //<permits a function call to be forwarded to an element
Proxy<T> operator=(T); //< permits the specific element to be replaced/assigned by another T.
}
interface Container<T>
{
Proxy<T> item(int index);
T item(int index); //<When T is a copy of the original value.
void item(int index, T new_value); //<where new_value is used to replace the old value
}
On peut soutenir qu'une fonction escroc pourrait encore jouer au chaos ici (avec assez d'efforts, la plupart des choses sont possibles), mais la copie sémantique et / ou le proxy réduisent les risques d'erreurs.
- débordement
- débordement
- les interactions avec le sous-élément sont vérifiées par type / vérifiables par type (dans les langues perdues, c'est une aubaine)
- L'élément réel peut ou non être résident en mémoire.
Getters / Setters privés
Il s'agit du dernier bastion de getters et setters travaillant directement sur le type. En fait, je n'appellerais même pas ces getters et setters mais des accesseurs et des manipulateurs.
Dans ce contexte, la manipulation d'une partie spécifique de la structure de données nécessite parfois / presque toujours / généralement une tenue de livres spécifique. Supposons que lorsque vous mettez à jour la racine d'une arborescence, le cache de surveillance doit être purgé, ou lorsque vous accédez à l'élément de données externe, un verrou doit être obtenu / libéré. Dans ces cas, il est logique d'appliquer le principal DRY et de répartir ces actions ensemble.
Dans le contexte privé, il est toujours possible pour les autres fonctions de la famille de contourner ces «getters et setters» et de manipuler la structure des données. C'est pourquoi je les considère davantage comme des accesseurs et des manipulateurs. Vous pouvez accéder directement aux données ou compter sur un autre membre de la famille pour obtenir cette partie correctement.
Getters / Setters protégés
Dans un contexte protégé, ce n'est pas très différent d'un contexte public. Les fonctions étrangères éventuellement escrocs veulent accéder à la structure de données. Donc non, s'ils existent, ils fonctionnent comme des getters / setters publics.
this->variable = x + 5
, ou appeler uneUpdateStatistics
fonction dans setter, et dans ces cas,classinstancea->variable = 5
cela causera des problèmes.