[Java] comment bien utiliser les getters et setters en général?

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

bonjour,

Dans mon projet d’informatique java j’ai beaucoup de classes et actuellement j’utilise tous le temps des getters et setters car tous mes attributs sont privés, cela pose souci d’écriture dès fois :

game.getWindow().getMap().getPlayer().setPos(4, 8);

J’ai lu que c’était une bonne pratique de toujours utiliser des getters/setters mais là je me pose question…

+0 -0

Dans ce cas pourquoi les mettre privés si tu leur donnes systématiquement une interface publique ?

entwanne

Alors très bonne question car j’ai appris dans un tuto Java qu’il fallait toujours mettre ses attributs privés et les écrire avec un tilde devant :

private int _monNombre;

et je pense que c’est parce que c’est une "bonne pratique". Je remarque donc que je dois avoir quelques lacunes en la matière, ou peut-être aurais je sauté une étape… ?

PS: je lis un tuto sur l’encapsulation, je crois que c’est ce qui me fait défaut…

https://www.commentcamarche.net/contents/808-poo-encapsulation-des-donnees

+0 -0

J’ai du nouveau!

J’ai trouvé un document de bonnes pratiques qui explique pourquoi il faudrait toujours mettre private:

Il ne faut pas déclarer de variables d’instances ou de classes publiques sans raison valable.

Il est préférable de restreindre l’accès à la variable avec un modificateur d’accès protected ou private et de déclarer des méthodes respectant les conventions instaurées dans les javaBeans : getXxx() ou isXxx() pour obtenir la valeur et setXxx() pour mettre à jour la valeur.

La création de méthodes sur des variables private ou protected permet d’assurer une protection lors de l’accès à la variable (déclaration des méthodes d’accès synchronized) et éventuellement un contrôle lors de la mise à jour de la valeur.

98.6.1. Le respect des règles d’encapsulation
https://www.jmdoudoux.fr/java/dej/chap-normes-dev.htm

Quelqu’un pourrais-t-il expliquer pourquoi cette convention?

L’idée est de protéger par l’encapsulation les invariants de tes objets (c’est-à-dire empêcher de casser l’état interne d’un objet en modifiant arbitrairement un attribut). C’est de là que viennent ce genre de conventions, qui sont plutôt contre-productives : tes invariants ne sont pas mieux protégés si tu modifies un attribut en y accédant directement ou par une méthode setXXX.

Il faut plutôt penser ta classe en terme d’interface et des actions qu’elle doit exposer, pas coller aux attributs qu’elle contient.

C’est une discussion qui revient régulièrement sur le forum, tu peux par exemple suivre les liens référencés sur ce post : https://zestedesavoir.com/forums/sujet/3117/question-sur-lencapsulation-des-attributs/#p56341

Merci, à ce propos:

C’est quoi un invariant ? Un attribut final ?

Et est-ce que mettre des attributs public c’est mal vu par apport aux bonnes pratiques, si j’ai aucune raison de les encapsuler?

Je pensais carrément à faire un truc style:

public class Player {

    public int pos_x;
    public int pos_y;

    // Laissons autre part le soin de vérifier que pos_X, pos_Y > 0

    // Avance de plusieurs pas en X et Y
    public void stepBy(int stepX, int stepY) {
        pos_x += stepX;
        pos_y += stepY;
    }
}

C’est quoi un invariant ? Un attribut final ?

marius007

Non, un invariant, c’est une propriété, au sens mathématique, que doit vérifier ton objet.

Dans l’exemple des coordonnées, il s’agit de la propriété "les deux types de coordonnées correspondent au même point". Dans l’exemple Player, il s’agit de la propriété "chaque coordonnées est positive".

Pour s’assurer qu’il n’est pas brisé par mégarde, il faut que les setters, qui modifient l’état de l’objet, assurent la conservation des invariants.

Dans ton exemple Player, ce n’est pas le cas, parce qu’il suffit de mettre un step suffisamment négatif pour que la position devienne négative. Il y différentes solutions qui permettent d’éviter cela : bloquer à zéro, empêcher l’application de step qui passent dans les négatifs, repasser à une autre valeur qui fait sens (par exemple dans une version du jeu Snake, traverser un mur amène au mur opposé)…

+0 -0

Beaucoup de choses ont été dites, je vais donc plutôt donner de la méta-information. Le problème principal de cette histoire de getter/setter, c’est que la programmation objet est souvent mal comprise et/ou mal appliquée. Je m’explique.

L’une des théories des langages objets, c’est que tu manipules des objets dont l’état et le fonctionnement internes sont inconnus de l’extérieur, et manipulé par des actions connues qui empêchent de casser l’état interne.

Tu es donc censé avoir un objet, avec son état interne représenté par divers champs tous privés pour éviter que l’on puisse manipuler directement son état. Tu le manipules via des actions (méthodes publiques) qui vérifient que l’action ne casse pas l’état interne. Ces actions peuvent être décrites dans un contrat d’interface.

(Je passe sur les notions d’objets immutables etc).

Ça, c’est la théorie, mais elle a deux failles :

  1. Il y a plein de cas où les actions manipulent presque un état interne directement – ou donnent cette impression1.
  2. Beaucoup de langages objet (dont Java) ne font pas distinction claire entre un objet et une structure de données, et utilisent un objet pour tout.

Or, un objet répond à la définition ci-dessus, mais une structure de données sert juste à ranger des données. La seule chose qui est intéressante là-dedans, c’est les données, donc l’état interne, tu n’as aucune manipulation quelconque. Sauf que comme tout le monde dit les champs doivent toujours être privés, tu te retrouves avec classes constituées uniquement de champs et de leurs getters/setters par défaut, sans le moindre contrôle. Ce qui est identique, mais plus lourd, à une classe avec tous les champs publics2.

C’est pour gérer ce cas qu’on a inventé des outils comme la data class en Kotlin, ou Lombok en Java.

En résumé, l’important est surtout de comprendre ce que tu fais, et pourquoi tu le fais. Ça ne sert jamais à rien d’appliquer une règle pour la règle, parce que c’est les bonnes pratiques. Il faut toujours appliquer une règle parce que c’est pertinent.


D’autre part :

Alors très bonne question car j’ai appris dans un tuto Java qu’il fallait toujours mettre ses attributs privés et les écrire avec un tilde devant :

private int _monNombre;

marius007

Est-ce que ton tuto est public ? Je serais curieux de savoir de quand il date, parce que la pratique de coller un souligné en début de champ est plutôt rare, et est généralement signe de :

  • Un vieux tuto et/ou projet,
  • ou un tuto adapté d’un autre langage et donc éloigné des spécificités de Java et donc à éviter,
  • ou un architecte maniaque avec des conventions inhabituelles sur le projet.

Les conventions de code utilisées en Java récent sont généralement proches de celles de Google.


  1. Exemple : un point sur un segment, représenté par un nombre X qui donne sa position et seul attribut du point vu de l’extérieur… sauf que si tu manipules ce nombre directement sans contrôles, tu peux l’avoir en-dehors du segment, et donc casser l’objet.

  2. À ceci près que c’est plus lourd à utiliser, mais plus facile à faire évoluer vers une structure qui contrôle l’intégrité de ses données, voire vers un « vrai » objet.

Merci pour toutes ces infos, je n’ai pas encore eu le temps de les intégrer mais je vais déjà poser une question en attendant d’avoir tout compris car je pense avoir trouvé un bon exemple d’objet à modéliser pour me permettre de comprendre tout ça.

Or, un objet répond à la définition ci-dessus, mais une structure de données sert juste à ranger des données. La seule chose qui est intéressante là-dedans, c’est les données, donc l’état interne, tu n’as aucune manipulation quelconque.

(et aussi Aabu parlait d’invariant, je m’explique…)

Imaginons que je veux modéliser un gaz parfait. L’état interne d’un gaz parfait est définit par ses variables d’état P, V, T et n. L’invariant à respecter (càd la condition mathématique pour que l’objet ne soit pas cassé) ici c’est la loi des gaz parfait : PV = nRT

Donc ça donne en code en deux mots :

public class GazParfait {

    private double P; // pression
    private double V; // volume
    //....

    // Getters et setters tels que PV = nRT à tout instant
}

….et d’où l’utilité de mettre les champs P, V, n et T en privé pour éviter de casser l’objet !

Cc’est bien ça le principe d’encapsulation pour l’exemple des gaz parfait? :ange:

Oui, c’est un bon exemple !

En passant sur les détails, on pourrait faire subir des transformations au gaz telles que des compression et détente (changer la pression donc) et observer l’effet sur la température.

Si on ne conservait pas l’invariant, le modèle serait physiquement faux, donc c’est important que l’objet le conserve.

+0 -0

Une autre raison de passer par le niveau d’indirection des getters/setters est la compatibilité : si plus tard on change le nom des champs, leur type, etc., souvent on peut toujours exporter les getter/setter d’avant qui font la conversion au vol.

Ça n’empêche pas que quand on veut juste représenter des données, l’approche orientée objet n’est pas très pratique et apporte beaucoup de lourdeur, surtout en Java. On peut y remédier en ayant des représentations standardisées de valeurs; typiquement pour les paires, les tuples.

cela pose souci d’écriture dès fois :

game.getWindow().getMap().getPlayer().setPos(4, 8);

J’ai lu que c’était une bonne pratique de toujours utiliser des getters/setters mais là je me pose question…

marius007

Il y a un principe qui s’appelle la loi de Demeter, qui dit que les gens qui écrivent des trucs à rallonge abominables comme ça iront en enfer.

C’est pas spécialement un histoire de getters/setters Sérieusement, l’idée c’est que quand tu écris une formule comme ça, ça sous-entend que

  • ton attribut game à une méthode gameWindow()
  • que cette methode retourne un objet à qui on peut appliquer getMap()
  • que le résultat à une méthode getPlayer()
  • etc.

En résumé ça veut dire que tu connais plein de choses sur les différents types qui interviennent là-dedans. Autrement dans dit, dans du code qui s’occupe de game (qu’il a sous la main), tu vas tripoter des trucs qui concernent des détails qui contiennent des sous-details et des sous-sous-détails.

Du coup, ca veut dire que le code que tu écris est complètement dépendant de la connaissance détaillée de beaucoup trop de choses.

Wikipedia : La loi de Déméter (en anglais Law of Demeter ou LoD), ou Principe de connaissance minimale est une règle de conception pour développer un logiciel, particulièrement du logiciel orienté objet. Cette règle a été inventée à la Northeastern University à Boston durant l’automne 1987, et peut être résumée en « Ne parlez qu’à vos amis immédiats ». La notion fondamentale est qu’un objet devrait faire aussi peu d’hypothèses que possible à propos de la structure de quoi que ce soit d’autre, y compris ses propres sous-composants.

+1 -0

Si jamais d’autre sont dans mon cas, alors je précise que j’ai tout de même été poser la question aux assistants chargés du projet et ils m’ont répondu qu’il faut systématiquement mettre des getters/setters, sinon on perd directement des points. Unique exception à ça: les membres static final qui peuvent être public.

J’ai demandé que se passe t’il si j’ai un getter/setter trivial, ils m’ont répondu que c’est pas possible car dans le setter ils faut toujours vérifier les informations! (par exemple au moins s’assurer que les quantités soient positives, ne dépasse pas un certain seuil…) et lever une Exception si dépassement de limites…

Je suppose que tu es dans un contexte scolaire ?

Le problème de l’enseignement de Java, c’est que c’est très majoritairement enseigné par des gens qui n’ont plus vu de véritable projet Java depuis des années – quand ils en ont vu un jour, certains n’ont jamais fait autre chose que de l’enseignement, et/ou ont inventé un cours de Java à partir d’un cours d’un autre langage. Quant aux chargés de TP/TD / assistants de projets, c’est souvent des étudiants eux-mêmes, qui ont encore moins de raison d’avoir vu un vrai projet.

En bref, ce qui est important c’est d’appliquer les règles que te demandent les profs, tout en sachant qu’elles n’ont du sens que dans ce contexte particulier.

assistants chargés du projet […] ils m’ont répondu qu’il faut systématiquement mettre des getters/setters, sinon on perd directement des points J’ai demandé que se passe t’il si j’ai un getter/setter trivial, ils m’ont répondu que c’est pas possible car dans le setter ils faut toujours vérifier les informations! (par exemple au moins s’assurer que les quantités soient positives, ne dépasse pas un certain seuil…) et lever une Exception si dépassement de limites…

marius007

Bin, faut croire que tes "assistants de projets" sont des guignols qui répètent bêtement ce que d’autres guignols leur ont raconté (genre élève de 2ième année qui sont assistants pour les 1ere année).

Pour rigoler, demande-leur à qui sert la visibilité "package", puisque d’après eux il faut tout mettre public ou privé…

Heureusement, ici on a des guignols professionnels :-)

+0 -0

Pas de chance ce sont eux qui vont noter la présentation du projet, donc je m’adapte

Pour la visibilité package, très bonne question, je sais que ça sert à ne rendre les attributs visible que dans le package où ils sont déclarés mais pourquoi mettre package si on peut mettre public? (si vous pouviez donner un exemple d’utilisation du mot package)

Et vos conseils professionnels me seront précieux pour l’avenir ! :)

+0 -0

Parce que la visibilité, c’est plus compliqué que "cette classe contre le reste du monde".

En pratique, pour arriver à réaliser un service, on a souvent besoin d’écrire plusieurs classes dont certaines ne sont que des classes auxiliaires qui n’ont pas à être publiques. Donc elles ne sont accessibles que par des classes du même groupe : le package.

Bonjour, ici est-ce une bonne manière de procéder pour utiliser les setters?

public class Chat {
    private String surnom;

    public Chat(String surnom) {
        this.setSurnom(surnom);
    }

    public String getSurnom() {
        return this.surnom;
    }

    public void setSurnom(String newSurnom) {
        if (newSurnom.length() > 0) {
            this.surnom = newSurnom;
        }
        else throw new RuntimeException("Surnom vide!");
    }
}

je précise ma question :

ligne 5 : est ce correct comme ça ou vaut-il mieux this.surnom = surnom; ?

ligne 16 : est ce OK de gérer des erreurs comme ça?

merci! :)

Bonjour,

ligne 5 : est ce correct comme ça ou vaut-il mieux this.surnom = surnom; ?

Ca dépend. En utilisant le setter, tu effectues le contrôle de validation dès la construction sans dupliquer le code.

Dans ce petit exemple c’est évidemment un avantage, et si tu suis la logique du DDD comme mode de développement, c’est un très très bon réflexe.

Selon les cas, ça peut toutefois poser problème: si les calculs sont lourds dans le setter, si la validation ne doit pas être faite à la construction mais à un autre moment, ou si tu dois renvoyer les erreurs en une seule fois de manière groupée à l’utilisateur. Dans ces cas tu ne peux pas valider les données directement dans les setters, tu as besoin d’une méthode spécifique pour ça.

ligne 16 : est ce OK de gérer des erreurs comme ça?

Mème réponse que ci-dessus: si c’est OK pour toi de valider un à un les attributs dès leur modification, oui et c’est un très bon réflexe; sinon, non. Pour les besoins de ton cours ça sera sûrement plutôt oui.

A cela près qu’il ne faut pas utiliser RuntimeException mais un type plus spécifique d’exception représentant plus précisément l’erreur: IllegalArgumentException (paramètre incorrect), IndexOutOfBoundsException (paramètre hors bornes admissibles), NoSuchElementException (élément non trouvé), IllegalStateException (modification de l’attribut au mauvais moment), etc. ou un type d’exception perso si tu en as un (ce n’est pas obligatoire, c’est seulement utile d’en créer un si tu peux apporter des informations plus précises sur l’erreur que par les types standards). En principe on n’utilise pas RuntimeException en tant que tel mais uniquement ses sous-classes.

Dans ton cas ici le type correct à utiliser serait donc sans doute IllegalArgumentException.

+1 -0
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