Question sur l'encapsulation des attributs

public ou private ?

Le problème exposé dans ce sujet a été résolu.

Salut,

Je code avec un ami un jeu en C++, et nous ne sommes pas d'accord sur un point, du coup on fait appel à votre expertise pour trancher. Notre problème est un sujet récurrent sur les forum, mais à chaque fois la question déclenche souvent des émeutes, j'espère que ça ne sera pas le cas ici :) . Dans tous les cours de C++ que j'ai pu lire, il est conseillé de mettre tous les attributs en private et d'éventuellement mettre des getter et setter. Ayant appris la programmation avec Python, je n'ai pas beaucoup de notions en encapsulation. Voici notre classe:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Rect
{
    public:
        Rect(Position pos, int w, int h);
        Rect(int x, int y, int w, int h);
        ~Rect();

        Position pos;
        int w;
        int h;
};

Mais Qames (celui avec qui je code le projet) me dit de mettre les attributs en private et de faire des getter et setter, sous prétexte que c'est plus sécurisé. Mais si le code des accesseurs sont "classiques" c'est à dire T getAttr() { return attr; } pour le getter et void setAttr(T val) { this->attr = val; } pour le setter, alors à quoi bon ? Ca revient au même, non ?

Merci d'avance,

AZ et Qames.

PS: Pour une classe aussi simple c'est pas plus facile de faire une struct ?

+0 -0

Salut,

Mais Qames (celui avec qui je code le projet) me dit de mettre les attributs en private et de faire des getter et setter, sous prétexte que c'est plus sécurisé. Mais si le code des accesseurs sont "classiques" c'est à dire T getAttr() { return attr; } pour le getter et void setAttr(T val) { this->attr = val; } pour le setter, alors à quoi bon ? Ca revient au même, non ?

AlphaZeta

En l'état actuel, oui, le résultat est identique. Cependant, si vous décidez de modifier le code par après et, par exemple, d'effectuer des vérifications et/ou modifications sur les valeurs fournies, alors l'utilisation des méthodes vous évitera de devoir modifier votre code. Maintenant, nous sommes d'accords : c'est assez peu probable dans ce cas ci.

PS: Pour une classe aussi simple c'est pas plus facile de faire une struct ?

AlphaZeta

J'aurais tendance à dire qu'une classe a effectivement peu d'intérêt dans cette situation. Toutefois, j'utilise pour ma part le C, il y a donc peut-être des motifs pour utiliser une classe qui m'échappent.

+1 -2

PS: Pour une classe aussi simple c'est pas plus facile de faire une struct ?

AlphaZeta

J'aurais tendance à dire qu'une classe a effectivement peu d'intérêt dans cette situation. Toutefois, j'utilise pour ma part le C, il y a donc peut-être des motifs pour utiliser une classe qui m'échappent.

Taurre

Je crois (les spécialistes me corrigeront au besoin) que en C++, une classe et une struct sont exactement la même chose, sauf que les membre d'une classe sont private par défaut, et les membres d'une struct sont en public par défaut.

+2 -0

En l'occurrence ici, ça touche plus du domaine de la structure de données (on définit ce qu'est un rectangle, et même carrément on ne définit pas d'opération élémentaires dessus) que de la classe : quel est le service rendu par la classe Rect ? Manipuler les Rect ? Dans ce cas c'est bien une structure de données.

Les getter/setter, comme dit par les voisins du dessus, sont réservés à des cas assez restreint, déjà où il n'y a matériellement pas le choix (temps, code déjà fait) et seulement si la classe est sujet à des vérifications/observations.

Ici, la vérification pourrait être que w et h sont positifs, mais elle peut être évitée en mettant des unsigned. Niveau vérifications/observations supplémentaires, la classe en elle même n'en a pas besoin, donc c'est une autre classe qui aurait besoin de ces vérifications => pas besoin.

En réalité, les getters et setters vont servir surtout dans ce type de classe: ils sont indépendants de l'implémentation.

Si pour accéder à la position x du rectangle je dois faire rect.pos.x, Demeter ne va pas être très content. Par contre avec rect.x(), je me fiche totalement des intermédiaires. En plus, manipuler x,y et w,h n'est pas symétrique. Autre exemple, si w et h sont regrouper dans une classe Bounds, il va falloir remplacer tous les rect.w en rect.bounds.w, pas s'il y a des getters/setters.

Ma solution préférée reste cependant l'usage de fonctions libres, Rect ne devient qu'un contexte de données qui n'est jamais directement manipulé. C'est aussi plus facile de faire de la programmation générique avec des fonctions libres (quand plusieurs classes Rectangle de différentes bibliothèques cohabitent dans l'application).

(Cela me fait vaguement penser à cette discussion.)

Je crois (les spécialistes me corrigeront au besoin) que en C++, une classe et une struct sont exactement la même chose, sauf que les membre d'une classe sont private par défaut, et les membres d'une struct sont en public par défaut.

Luthaf

Ah ! À la lecture de la norme C++-11 tu sembles avoir raison. Il en va appremment de même pour les unions :

A union is a class defined with the class-key union; it holds only one data member at a time […].

C++-11, doc. N3242, § 9, al. 5

A standard-layout class is a class that:

  • has no non-static data members of type non-standard-layout class (or array of such types) or reference,
  • has no virtual functions (10.3) and no virtual base classes (10.1),
  • has the same access control (Clause 11) for all non-static data members,
  • has no non-standard-layout base classes,
  • either has no non-static data members in the most derived class and at most one base class with non-static data members, or has no base classes with non-static data members, and
  • has no base classes of the same type as the first non-static data member.

A standard-layout struct is a standard-layout class defined with the class-key struct or the class-key class.

C++-11, doc. N3242, § 9, al. 7 et 8

Merci pour cette précision.

+0 -0

Les setters sont des décapsuleurs.

La vraie question à te poser est : "est-ce que la classe a (/pourra avoir) des invariants?". Si la réponse est non, alors setters et getters n'ont aucun sens. C'est particulièrement vrai avec tous les exemples de type point. Et que l'on passe par p.x(), ou p.x, Déméter ne s'en portera pas mieux, à contrario de passer par make_point(x,y), p1 + 42.12 * (p1 - p2), …

Bref, l'objectif de l'encapsulation n'est pas de cacher les données pour le plaisir de les cacher, c'est de garantir le respect des invariants.

Bref. J'ai plein de lecture sur le sujet, et j'ai la flemme de les trier alors que cela a déjà été fait ici même:

@lmghs: tu penses en terme de service et non de données :).

C'est pénible d'avoir une structure de donnée qui, suite a l'ajout d'un intermédiaire, modifie une grande quantité de code. En plus, on utilise différemment les facilitateurs¹ et les variables (donc des syntaxes différentes). On finit par ne plus savoir qui est fonction et qui est variable.

Note que pour les objets de donnée, je n'imagine pas de setter mais un observer qui retourne une référence (ou un proxy): rect.x() += 2;

¹ facilitateurs: fonction membre qui retourne un ensemble restreint des données (ex: fonction position() d'une classe rectangle {x,y,z,h}).

Je sais aussi penser en termes de données. Et dans ce cas là, il ne s'agit que de données et pas de trucs bâtards de type données derrière une fonction qui rajoute un intermédiaire pour faire plaisir à ceux qui n'ont pas compris ce qu'est l'encapsulation.

rect.x() += 2 me parait être ce qu'il y a probablement de pire en terme de choix. À moins d'un encodage vraiment particulier de l'abscisse, cette surcouche n'apporte rien.

PS: je dois faire parti des rares personnes qui préfèrent voir un code planter si l'interface d'accès à une donnée change parce que la représentation de la donnée a changé. Cela me permet de juger s'il y a besoin de revoir les accès ou pas. Maintenant, c'est une situation que je n'ai pratiquement jamais rencontré. Je suis de fait prêt à payer le prix d'un changement d'interface de temps à autres : le prix est largement amorti depuis le temps. Mais bon, comme tu l'as dit, je suis principalement dans un paradigme de services -> d'où que mes interfaces ne dépendent pas de données, et que donc je n'ai pas le problème du changement de représentation qui influe l'interface.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte