web-dev-qa-db-fra.com

Les getters et les setters ont-ils une mauvaise conception? Avis contradictoire vu

Je travaille actuellement sur un jeu simple dans Java avec plusieurs modes différents. J'ai étendu une classe de jeu principale afin de placer la logique principale dans les autres classes. Malgré cela, la classe de jeu principale est encore assez lourde.

Après avoir jeté un coup d'œil à mon code, la majorité d'entre eux étaient des Getters et des Setters (60%) par rapport au reste qui est vraiment nécessaire pour la logique du jeu.

Quelques recherches sur Google ont prétendu que les Getters et les Setters étaient diaboliques, tandis que d’autres ont affirmé qu’elles étaient nécessaires à une bonne pratique OO) et à de bons programmes.

Donc qu'est ce que je devrais faire? Que devrait-il être? Devrais-je changer mes Getters et Setters pour mes variables privées, ou devrais-je rester avec eux?

223
Mike B

Il existe également le point de vue selon lequel, la plupart du temps, l’utilisation de séparateurs rompt l’encapsulation en vous permettant de définir des valeurs dénuées de sens. Comme exemple très évident, si vous avez un compteur de score sur le jeu qui ne fait que monter, au lieu de

// Game
private int score;
public void setScore(int score) { this.score = score; }
public int getScore() { return score; }
// Usage
game.setScore(game.getScore() + ENEMY_DESTROYED_SCORE);

cA devrait etre

// Game
private int score;
public int getScore() { return score; }
public void addScore(int delta) { score += delta; }
// Usage
game.addScore(ENEMY_DESTROYED_SCORE);

Ceci est peut-être un peu un exemple facile. Ce que j'essaie de dire, c'est que discuter de getter/setters par rapport aux champs publics masque souvent des problèmes plus importants avec des objets manipulant de manière intime l'état interne de chacun, et donc trop étroitement couplés.

L'idée est de faire des méthodes qui font directement ce que vous voulez faire. Un exemple serait de définir le statut "vivant" des ennemis. Vous pourriez être tenté d'avoir une méthode setAlive (boolean alive). Au lieu de cela, vous devriez avoir:

private boolean alive = true;
public boolean isAlive() { return alive; }
public void kill() { alive = false; }

La raison en est que si vous modifiez l’implémentation de sorte que les choses n’ont plus de valeur booléenne "en vie" mais plutôt de "points de repère", vous pouvez la modifier sans rompre le contrat des deux méthodes que vous avez écrites précédemment:

private int hp; // Set in constructor.
public boolean isAlive() { return hp > 0; } // Same method signature.
public void kill() { hp = 0; } // Same method signature.
public void damage(int damage) { hp -= damage; }
344
Zarkonnen
  • Très diabolique: les champs publics.
  • Quelque chose de mal: Getters et setters là où ils ne sont pas nécessaires.
  • Bon: les getters et les setters ne sont requis que là où ils sont réellement nécessaires - faites en sorte que le type expose un comportement "plus important" qui se produit utilise son état, plutôt que de simplement traiter le type en tant que référentiel d'état à manipuler par d'autres types.

Cela dépend vraiment de la situation, bien que parfois vous souhaitiez simplement simplement un objet de données muet.

180
Jon Skeet

Vous avez déjà eu beaucoup de bonnes réponses à ce sujet, je vais donc donner mes deux sous. Les Getters et les setters sont très, très pervers. Ils vous permettent essentiellement de faire semblant de cacher les éléments internes de votre objet lorsque la plupart du temps, tout ce que vous avez fait est jeté dans un code redondant qui ne fait rien pour masquer l'état interne. Pour un simple POJO, il n'y a aucune raison pour que getName () et setName () ne puissent pas être remplacés par obj.name = "Tom".

Si l'appel de méthode remplace simplement l'attribution, le gain de code est tout ce que vous avez gagné en préférant l'appel à la méthode. Malheureusement, le langage a enchâssé l'utilisation de getters et de setters dans la spécification JavaBeans, donc les programmeurs de Java sont obligés de les utiliser, même si cela n'a aucun sens.

Heureusement, Eclipse (et probablement d'autres IDE également) vous permet de les générer automatiquement. Et pour un projet amusant, j’ai construit un générateur de code pour eux dans XSLT. Mais s'il y avait une chose dont je voudrais me débarrasser à Java, c'est la dépendance excessive à l'égard des accesseurs et des passeurs.

46
rtperson

Les getters et les setters appliquent le concept de encapsulation dans la programmation orientée objet.

En cachant les états de l'objet du monde extérieur, l'objet est vraiment en charge de lui-même et ne peut être modifié de manière non intentionnelle. L'objet ne peut être manipulé que par le biais de méthodes publiques exposées, telles que les accesseurs et les setters.

Il y a quelques avantages à avoir des getters et des setters:

1. Autoriser les modifications futures sans modifier le code qui utilise la classe modifiée.

L’un des gros avantages de l’utilisation d’un getter et d’un setter est qu’une fois les méthodes publiques définies et qu’il faut modifier l’implémentation sous-jacente (par exemple, trouver un bogue à corriger, en utilisant un algorithme différent pour améliorer les performances). , etc.), en ayant les getters et les setters comme le seul moyen de manipuler l’objet, le code existant ne sera pas cassé et fonctionnera comme prévu même après le changement.

Par exemple, supposons qu'il existe une méthode setValue qui définit la variable privée value dans un objet:

public void setValue(int value)
{
    this.value = value;
}

Mais ensuite, il y avait une nouvelle exigence qui devait garder la trace du nombre de fois que value avait été changé. Avec le passeur en place, le changement est assez simple:

public void setValue(int value)
{
    this.value = value;
    count++;
}

Si le champ value était public, il n'existe pas de moyen facile de revenir plus tard et d'ajouter un compteur qui enregistre le nombre de modifications de la valeur. Par conséquent, avoir des accesseurs et des setters est un moyen de "préparer le futur" de la classe pour les changements qui pourraient survenir plus tard.

2. Appliquer les moyens par lesquels l’objet peut être manipulé.

Une autre façon dont les accesseurs et les calculateurs sont utiles consiste à appliquer les méthodes de manipulation de l'objet. Par conséquent, l'objet contrôle son propre état. Avec les variables publiques d'un objet exposé, il peut facilement être corrompu.

Par exemple, un objet ImmutableArray contient un tableau int appelé myArray. Si le tableau était un champ public, il ne serait tout simplement pas immuable:

ImmutableArray a = new ImmutableArray();
int[] b = a.myArray;
b[0] = 10;      // Oops, the ImmutableArray a's contents have been changed.

Pour implémenter un tableau vraiment immuable, un getter pour le tableau (méthode getArray) devrait être écrit pour qu'il retourne une copie de son tableau:

public int[] getArray()
{
    return myArray.clone();
}

Et même si ce qui suit se produit:

ImmutableArray a = new ImmutableArray();
int[] b = a.getArray();
b[0] = 10;      // No problem, only the copy of the array is affected.

Le ImmutableArray est en effet immuable. Exposer les variables d'un objet lui permettra d'être manipulé de manière non intentionnelle, mais en exposant seulement certaines manières (accesseurs et régleurs), l'objet peut être manipulé de la manière voulue.

Je suppose qu'avoir des getters et des setters serait plus important pour les classes faisant partie d'une API qui sera utilisée par d'autres, car cela permet de garder l'API intacte et inchangée tout en permettant des modifications dans l'implémentation sous-jacente.

Avec tous les avantages des getters et des setters, si le getter ne fait que renvoyer la valeur de la variable privée et que le setter accepte simplement une valeur et l'assigne à une variable privée, il semble que les accesseurs et le poseur sont simplement étrangers et gaspillage. Si la classe doit uniquement être utilisée en interne par une application qui ne sera pas utilisée par d'autres, l'utilisation intensive des accesseurs et des paramètres peut ne pas être aussi importante que lors de l'écriture d'une API publique.

28
coobird

Ils sont absolument pervers.

@coobird malheureusement, ils ne font absolument pas "appliquer le concept d'encapsulation", ils ne font que vous faire croire que vous encapsulez des données alors que vous les exposez via une propriété avec des illusions de grandeur de méthode. Tout ce qu'un getter/setter fait sur un terrain public fait mieux.

Premièrement, si vous voulez des données publiques, rendez-les publiques, supprimez les méthodes getter & setter afin de réduire le nombre de méthodes que le client doit parcourir et de le simplifier sur le plan cognitif pour le client, afin de changer sa valeur, par exemple.

object.field = value;

au lieu de la plus intense cognitivement

object.setField(value);

où le client doit maintenant vérifier la méthode getter/setter pour voir si elle a des effets secondaires.

Deuxièmement, si vous avez vraiment besoin de faire autre chose dans la méthode, pourquoi appelez-la méthode get/set quand elle a plus de responsabilités que simplement obtenir ou définir? Suivez le SRP ou appelez la méthode quelque chose qui vous dit réellement ce que le entier méthode est comme les exemples de Zarkonnen, il a mentionné par exemple.

public void kill(){
    isAlive = false;
    removeFromWorld(this);
}

au lieu de

public void setAlive(boolean isAlive){
    this.isAlive = isAlive;
    if (isAlive)
        addToWorld(this);
    else
        removeFromWorld(this);
}

où la méthode setAlive (boolean) indique-t-elle au client qu’elle aura pour effet secondaire de supprimer l’objet du monde? Pourquoi le client devrait-il avoir une connaissance du champ isAlive? De plus, que se passe-t-il lorsque l'objet est ré-ajouté au monde, devrait-il être réinitialisé? pourquoi le client se soucierait-il de cela?

IMHO la morale est de nommer des méthodes pour dire exactement ce qu'ils font, suivre le PÉR et se débarrasser des accesseurs. S'il y a des problèmes sans accesseurs/régleurs, demandez aux objets de faire leur propre travail dans leur propre classe au lieu d'essayer de faire des choses avec eux dans d'autres classes.

voila mon coup de gueule, désolé pour ça;)

27
nathan

C'est une pente glissante.

Un simple objet Transfer (ou Parameter) peut avoir pour seul objectif de contenir certains champs et de fournir leurs valeurs à la demande. Cependant, même dans ce cas dégénéré, on pourrait soutenir que l'objet doit être immuable - configuré dans le constructeur et exposant uniquement les méthodes get....

Il y a aussi le cas d'une classe qui expose des "boutons de contrôle"; L'interface utilisateur de votre autoradio peut probablement être comprise comme exposant quelque chose comme getVolume, setVolume, getChannel et setChannel, mais sa véritable fonctionnalité est de recevoir des signaux et d'émettre du son. Mais ces boutons n'exposent pas beaucoup de détails d'implémentation; vous ne savez pas, à l'aide de ces fonctions d'interface, si la radio est constituée de transistors, principalement de logiciels, ou de tubes à vide.

Plus vous commencez à penser à un objet en tant que participant actif à une tâche de domaine problématique, plus vous penserez qu'il est demandé à faire quelque chose au lieu de lui demander de vous en parler. état interne, ou en lui demandant ses données afin autre code peut faire quelque chose avec ces valeurs.

Si démoniaque"? Pas vraiment. Mais chaque fois que vous êtes enclin à mettre une valeur et à exposer les deux méthodes get... et set... sur cette valeur, demandez-vous pourquoi et quelle est réellement la responsabilité de cet objet. . Si la seule réponse que vous puissiez vous donner est: "Pour conserver cette valeur pour moi", alors peut-être quelque chose à côté OO se passe ici.

19
joel.neely

Votre classe de jeu suit probablement le objet divin antipattern si elle expose autant de variables. Il n'y a rien de mal avec les accesseurs et les setters (bien que leur verbosité dans Java puisse être un peu ennuyeux); dans une application bien conçue où chaque classe a une fonctionnalité clairement séparée, vous n'aurez pas besoin de dizaines de fonctions. eux dans une seule classe.

Edit: Si le but principal pour les accesseurs est de "configurer" la classe de jeu (je comprends votre commentaire de cette façon), alors votre Vous n’avez pas besoin des accesseurs (c’est parfaitement correct pour une classe d’accéder à ses propres variables privées sans utiliser les méthodes get), et vous pouvez probablement réduire beaucoup d’installateurs en "group setters" qui définissent plusieurs variables qui appartiennent conceptuellement.

15
Michael Borgwardt

La présence de getter et de setters a tendance à indiquer (une "odeur" si vous êtes dans ce genre de langue d'école primaire) qu'il existe un problème de conception. Les getters et les setters triviaux se distinguent à peine des terrains publics. En règle générale, le code opérant sur les données appartiendra à une classe différente: une encapsulation médiocre et ce que vous attendez de programmeurs pas à l'aise avec OO.

Dans certains cas, les getters et les setters vont bien. Mais en règle générale, un type avec à la fois des accesseurs et des setters indique des problèmes de conception. Les Getters travaillent pour l'immutabilité; les setters travaillent pour "dit ne demande pas". L'immuabilité et "ne demandez pas" sont de bons choix de conception, tant qu'elles ne sont pas appliquées dans un style qui se chevauche.

11
Tom Hawtin - tackline

Mon opinion est que les getters et les setters sont une exigence pour de bons programmes. Restez avec eux, mais n'écrivez pas de getters/setters inutiles - il n'est pas toujours nécessaire de traiter directement toutes les variables.

10
Joonas Pulakka

Je ne pense pas vraiment qu'ils sont pervers. Mais j'aimerais vivre dans un monde où je ne devrais jamais les utiliser à moins d'en avoir vraiment besoin.

Un exemple que j'ai lu ci-dessus était future-proofing votre code. Par exemple:

public void setValue(int value)
{
    this.value = value;
}

Ensuite, les exigences changent et vous devez suivre combien de fois la valeur a été définie.

Alors:

public void setValue(int value)
{
    this.value = value;
    count++;
}

C'est beau. J'ai compris. Toutefois, dans Ruby, les éléments suivants ne serviraient-ils pas le même objectif?

someobject.my_value = 100

Plus tard, vous devez suivre le nombre de fois my_value a été mis en. Eh bien, ne pouvez-vous pas simplement remplacer le paramètre PUIS et uniquement PUIS ?

def my_value=(value)
    @my_value = value
    @count++
end

Je suis tout pour le beau code mais je dois l’avouer, regardant à travers les montagnes de Java classes que nous avons et voyant littéralement des milliers et des milliers de lignes de code qui ne sont rien mais un getter/setters basique est moche et ennuyeux.

Lorsque je développais en C # à plein temps, nous utilisions tout le temps des propriétés publiques et ne personnalisions ou utilisaient des accesseurs personnalisés qu'en cas de besoin. Travaillé comme un charme et ça n'a rien cassé.

9
cbmeeks

Comme toujours la seule réponse est: ça dépend. Si vous êtes le seul à toucher le code, vous pouvez faire tout ce que vous voulez, y compris des raccourcis.

L'un des avantages de l'utilisation des paramètres est que les contrôles ne doivent être effectués qu'à un seul emplacement de votre code.

Vous voudrez peut-être accorder une plus grande attention à ce qui est réellement défini et défini par ces méthodes. Si vous les utilisez pour fournir un accès à des valeurs constantes, il est probablement préférable d'utiliser des constantes.

4
Jeroen van Bergen

Cela dépend du langage de programmation en question. Votre question est formulée dans le contexte de Java, où il semble que les accesseurs et les setters sont généralement considérés comme une bonne chose.

En revanche, dans le monde Python, ils sont généralement considérés comme des mauvais styles: ils ajoutent des lignes au code sans ajouter de fonctionnalité. Lorsque Python les programmeurs en ont besoin, ils peuvent utiliser la métaprogrammation pour détecter et/ou définir les attributs d'objet.

Dans Java (du moins la version de Java que j'ai apprise il y a un peu plus de dix ans), cela n'était pas possible. Ainsi, dans Java, il est généralement préférable d’utiliser religieusement les accesseurs et les setters, de sorte que vous puissiez, le cas échéant, remplacer l’accès aux variables.

(Cela ne rend pas nécessairement Python nécessairement meilleur que Java, mais simplement différent.)

3
user25148

Just FYI: En plus de toutes les excellentes réponses de ce fil, rappelez-vous que de toutes les raisons que vous pouvez invoquer pour ou contre les accesseurs, la performance n'en est pas une (comme certains pourraient le croire). La machine virtuelle Java est assez intelligente pour intégrer des accesseurs/régleurs triviaux (même des non-final, tant qu'ils ne sont pas réellement remplacés).

2
gustafc

Vous voudrez peut-être remplacer certaines de vos classes par des classes de valeur. Cela vous permettra de supprimer le getter et d’éviter les problèmes lorsque vous modifiez le contenu.

1
David Segonds

Si vous avez besoin d'un accès externe à des valeurs individuelles de champs, utilisez des getters et/ou des setters. Sinon, ne le faites pas. N'utilisez jamais de champs publics. C'est aussi simple que ça! (Ok, ce n'est jamais ça simple, mais c'est une bonne règle de base).

En général, vous devriez également trouver que vous devez fournir un setter beaucoup moins souvent qu'un getter - surtout si vous essayez de rendre vos objets immuables - ce qui est une bonne chose (mais pas toujours le meilleur choix) - mais même si ce n'est pas le cas.

1
philsquared

J'ai programmé en Java il y a quelques mois et j'ai appris que nous ne devrions utiliser des accesseurs/installateurs que lorsque cela est nécessaire pour l'application

s'amuser :)

0
Giancarlo