it-swarm.dev

Quand les getters et setters sont-ils justifiés

Les getters et les setters sont souvent critiqués comme n'étant pas des OO appropriés. D'un autre côté, la plupart des codes OO que j'ai vus ont de nombreux getters et setters.

Quand les getters et setters sont-ils justifiés? Essayez-vous d'éviter de les utiliser? Sont-ils surutilisés en général?

Si votre langue préférée a des propriétés (la mienne en a), de telles choses sont également considérées comme des getters et des setters pour cette question. Ils sont la même chose du point de vue de la méthodologie OO. Ils ont juste une syntaxe plus agréable.

Sources de la critique Getter/Setter (certaines tirées de commentaires pour leur donner une meilleure visibilité):

Pour formuler la critique simplement: les Getters and Setters vous permettent de manipuler l'état interne des objets depuis l'extérieur de l'objet. Cela viole l'encapsulation. Seul l'objet lui-même doit se soucier de son état interne.

Et un exemple de version procédurale du code.

struct Fridge
{
    int cheese;
}

void go_shopping(Fridge fridge)
{
     fridge.cheese += 5;
}

Version Mutator du code:

class Fridge
{
     int cheese;

     void set_cheese(int _cheese) { cheese = _cheese; }
     int get_cheese() { return cheese; }
 }

void go_shopping(Fridge fridge)
{
     fridge.set_cheese(fridge.get_cheese() + 5);        
}

Les getters et setters ont rendu le code beaucoup plus compliqué sans permettre une encapsulation appropriée. Parce que l'état interne est accessible à d'autres objets, nous ne gagnons pas beaucoup en ajoutant ces getters et setters.

La question a déjà été discutée sur Stack Overflow:

179
Winston Ewert

Avoir des getters et des setters ne rompt pas en soi l'encapsulation. Ce qui rompt l'encapsulation, c'est l'ajout automatique d'un getter et d'un setter pour chaque membre de données (chaque champ, dans Java jargon), sans y penser). mieux que de rendre tous les membres de données publics, ce n'est qu'un petit pas.

Le point d'encapsulation n'est pas que vous ne devriez pas être en mesure de connaître ou de modifier l'état de l'objet depuis l'extérieur de l'objet, mais que vous devez avoir un policy raisonnable pour le faire.

  • Certains membres de données peuvent être entièrement internes à l'objet et ne doivent avoir ni getters ni setters.

  • Certains membres de données doivent être en lecture seule, ils peuvent donc avoir besoin de getters mais pas de setters.

  • Certains membres de données peuvent devoir être cohérents entre eux. Dans un tel cas, vous ne fourniriez pas de setter pour chacun, mais une seule méthode pour les définir en même temps, afin que vous puissiez vérifier la cohérence des valeurs.

  • Certains membres de données peuvent seulement avoir besoin d'être modifiés d'une certaine manière, comme incrémentés ou décrémentés d'un montant fixe. Dans ce cas, vous fourniriez une méthode increment() et/ou decrement(), plutôt qu'un setter.

  • Pourtant, d'autres peuvent avoir besoin d'être en lecture-écriture et auraient à la fois un getter et un setter.

Prenons un exemple de class Person. Disons qu'une personne a un nom, un numéro de sécurité sociale et un âge. Disons que nous n'autorisons jamais les gens à changer de nom ou de numéro de sécurité sociale. Cependant, l'âge de la personne doit être incrémenté de 1 chaque année. Dans ce cas, vous fourniriez un constructeur qui initialiserait le nom et le SSN aux valeurs données, et qui initialiserait l'âge à 0. Vous fourniriez également une méthode incrementAge(), qui augmenterait l'âge par 1. Vous fournirez également des getters pour les trois. Aucun poseur n'est requis dans ce cas.

Dans cette conception, vous autorisez l'inspection de l'état de l'objet de l'extérieur de la classe et vous permettez qu'il soit modifié de l'extérieur de la classe. Cependant, vous ne permettez pas que l'état soit modifié arbitrairement. Il existe une politique qui stipule effectivement que le nom et le SSN ne peuvent pas être modifiés du tout et que l'âge peut être incrémenté d'un an à la fois.

Supposons maintenant qu'une personne ait également un salaire. Et les gens peuvent changer d'emploi à volonté, ce qui signifie que leur salaire changera également. Pour modéliser cette situation, nous n'avons pas d'autre moyen que de fournir une méthode setSalary()! Permettre de modifier le salaire à volonté est une politique parfaitement raisonnable dans ce cas.

Au fait, dans votre exemple, je donnerais à la classe Fridge les méthodes putCheese() et takeCheese(), au lieu de get_cheese() et set_cheese(). Ensuite, vous auriez toujours l'encapsulation.


public class Fridge {
  private List objects;
  private Date warranty;

  /** How the warranty is stored internally is a detail. */
  public Fridge( Date warranty ) {
    // The Fridge can set its internal warranty, but it is not re-exposed.
    setWarranty( warranty );
  }

  /** Doesn't expose how the fridge knows it is empty. */
  public boolean isEmpty() {
    return getObjects().isEmpty();
  }

  /** When the fridge has no more room... */
  public boolean isFull() {
  }

  /** Answers whether the given object will fit. */
  public boolean canStore( Object o ) {
    boolean result = false;

    // Clients may not ask how much room remains in the fridge.
    if( o instanceof PhysicalObject ) {
      PhysicalObject po = (PhysicalObject)o;

      // How the fridge determines its remaining usable volume is a detail.
      // How a physical object determines whether it fits within a specified
      // volume is also a detail.
      result = po.isEnclosedBy( getUsableVolume() );
    }

     return result;
  }

  /** Doesn't expose how the fridge knows its warranty has expired. */
  public boolean isPastWarranty() {
    return getWarranty().before( new Date() );
  }

  /** Doesn't expose how objects are stored in the fridge. */
  public synchronized void store( Object o ) {
    validateExpiration( o );

    // Can the object fit?
    if( canStore( o ) ) {
      getObjects().add( o );
    }
    else {
      throw FridgeFullException( o );
    }
  }

  /** Doesn't expose how objects are removed from the fridge. */
  public synchronized void remove( Object o ) {
    if( !getObjects().contains( o ) ) {
      throw new ObjectNotFoundException( o );
    }

    getObjects().remove( o );

    validateExpiration( o );
  }

  /** Lazily initialized list, an implementation detail. */
  private synchronized List getObjects() {
    if( this.list == null ) { this.list = new List(); }
    return this.list;
  }

  /** How object expiration is determined is also a detail. */
  private void validateExpiration( Object o ) {
    // Objects can answer whether they have gone past a given
    // expiration date. How each object "knows" it has expired
    // is a detail. The Fridge might use a scanner and
    // items might have embedded RFID chips. It's a detail hidden
    // by proper encapsulation.
    if( o implements Expires && ((Expires)o).expiresBefore( today ) ) {
      throw new ExpiredObjectException( o );
    }
  }

  /** This creates a copy of the warranty for immutability purposes. */
  private void setWarranty( Date warranty ) {
    assert warranty != null;
    this.warranty = new Date( warranty.getTime() )
  }
}
163
Dima

La raison fondamentale des getters et setters dans Java est très simple:

  • Vous pouvez uniquement spécifier méthodes, pas les champs, dans une interface.

Par conséquent, si vous souhaitez autoriser un champ à traverser l'interface, vous aurez besoin d'un lecteur et d'une méthode d'écriture. Ceux-ci sont traditionnellement appelés getX et setX pour le champ x.

42
user1249

De http://www.adam-bien.com/roller/abien/entry/encapsulation_violation_with_getters_and

Style JavaBean:

connection.setUser("dukie");
connection.setPwd("duke");
connection.initialize();

Style OO:

connection.connect("dukie","duke");

Eh bien, je préfère clairement cette dernière approche; il ne saigne pas les détails de l'implémentation, il est plus simple et plus concis, et toutes les informations nécessaires sont incluses avec l'appel de méthode, il est donc plus facile de bien faire les choses. Je préfère également définir des membres privés à l'aide de paramètres dans le constructeur, dans la mesure du possible.

Votre question est: quand un getter/setter est-il justifié? Peut-être quand un changement de mode est nécessaire, ou si vous devez interroger un objet pour obtenir des informations.

myObject.GetStatus();
myObject.SomeCapabilitySwitch = true;

En y réfléchissant, lorsque j'ai commencé à coder en C #, j'ai écrit beaucoup de code dans le style Javabeans illustré ci-dessus. Mais au fur et à mesure que j'ai acquis de l'expérience dans le langage, j'ai commencé à définir davantage de membres dans le constructeur et à utiliser des méthodes qui ressemblaient davantage au style ci-dessus OO style.

20
Robert Harvey

Quand les getters et setters sont-ils justifiés?

Lorsque le comportement "get" et "set" correspond réellement au comportement de votre modèle, ce qui est pratiquement jamais .

Toute autre utilisation n'est qu'une triche car le comportement du domaine métier n'est pas clair.

Modifier

Cette réponse pourrait avoir semblé désinvolte, alors laissez-moi développer. Les réponses ci-dessus sont pour la plupart correctes mais se concentrent sur les paradigmes de programmation de OO design et certains ce qui manque la vue d'ensemble. D'après mon expérience, cela conduit les gens à penser qu'éviter les gettings et les setters est une règle académique pour OO langages de programmation (par exemple, les gens pensent que remplacer les getters par des propriétés est génial)

En fait, c'est une conséquence du processus de conception. Vous n'avez pas besoin d'entrer dans les interfaces et l'encapsulation et les modèles, en vous demandant si cela casse ou non ces paradigmes et ce qui est bon ou mauvais OO programmation. Le seul point qui en fin de compte, c'est que s'il n'y a rien dans votre domaine qui fonctionne comme ça en les mettant, vous ne modélisez pas votre domaine.

La réalité est qu'il est très peu probable qu'un système dans votre espace de domaine ait des getters et setters. Vous ne pouvez pas vous approcher de l'homme ou de la femme qui est responsable de la paie et simplement dire "Fixez ce salaire à X" ou "Obtenez-moi ce salaire" . Un tel comportement n'existe tout simplement pas

Si vous mettez cela dans votre code, vous ne concevez pas votre système pour qu'il corresponde au modèle de votre domaine. Oui, cela rompt les interfaces et l'encapsulation, mais ce n'est pas le but. Le fait est que vous modélisez quelque chose qui n'existe pas.

De plus, vous manquez probablement une étape ou un processus important, car il y a probablement une raison pour laquelle je ne peux pas simplement monter pour payer et dire fixer ce salaire à X.

Lorsque les gens utilisent des getters et des setters, ils ont tendance à pousser les règles de ce processus au mauvais endroit. Cela s'éloigne encore plus de votre domaine. En utilisant l'exemple du monde réel, c'est comme un salaire en supposant que la personne aléatoire qui est entrée a la permission d'obtenir ces valeurs sinon elle ne les demanderait pas. Non seulement ce n'est pas la façon dont le domaine est, mais il ment en fait sur la façon dont le domaine est.

14
Cormac Mulhall

En règle générale, les getters et setters sont une mauvaise idée. Si un champ ne fait pas logiquement partie de l'interface et que vous le rendez privé, c'est bien. Si elle fait logiquement partie de l'interface et que vous la rendez publique, c'est bien. Mais si vous le rendez privé et que vous vous retournez et le rendez à nouveau public en fournissant un getter et un setter, vous revenez à votre point de départ, sauf que votre code est maintenant plus verbeux et obscurci.

Évidemment, il y a des exceptions. En Java, vous devrez peut-être utiliser des interfaces. La bibliothèque standard Java Java a des exigences de compatibilité ascendante si extrêmes qu'elles l'emportent sur les mesures normales de la qualité du code. Il est même possible que vous ayez réellement affaire au cas légendaire mais rare où il y a de fortes chances que vous puissiez remplacer plus tard un champ stocké par un calcul à la volée sans autrement casser l'interface. Mais ce sont des exceptions. Les getters et setters sont un anti-modèle qui nécessite une justification spéciale.

11
rwallace

si le champ est accessible directement ou via la méthode n'est pas vraiment important.

Les invariants de classe (utiles) sont importants. Et pour les préserver, il nous faut parfois ne pas pouvoir changer quelque chose de l'extérieur. Par exemple. si nous avons la classe Square avec une largeur et une hauteur séparées, la modification de l'une d'elles la fait devenir autre chose que square. Nous avons donc besoin de la méthode changeSide Si c'était Rectangle, nous pourrions avoir des setters/public field. Mais setter que testerait si son supérieur à zéro serait mieux.

Et dans les langages concrets (par exemple Java), nous avons besoin de ces méthodes (interfaces). Et une autre raison de la méthode est la compatibilité (source et binaire). Il est donc plus facile de les ajouter, puis de se demander si le domaine public suffirait.

btw. J'aime utiliser des classes de détention de valeur immuable simples avec des champs finaux publics.

3
user470365

Vous voudrez peut-être changer vos internes en tout ce qui reste tout en gardant les interfaces les mêmes. Si vos interfaces ne varient pas, celles que vous codez ne se casseront pas. Vous pouvez toujours changer vos internes comme vous le souhaitez.

2
George Silva

Considérons une classe Size qui encapsule la largeur et la hauteur. Je peux éliminer les setters en utilisant le constructeur mais comment cela m'aide-t-il à dessiner un rectangle avec Size? La largeur et la hauteur ne sont pas des données internes à la classe; ce sont des données partagées qui doivent être disponibles pour les consommateurs de Size.

Les objets sont constitués de comportements et d'état - ou d'attributs. S'il n'y a pas d'état exposé, seuls les comportements sont publics.

Sans état, comment classeriez-vous une collection d'objets? Comment rechercheriez-vous une instance spécifique d'un objet? Si vous utilisez uniquement des constructeurs, que se passe-t-il lorsque votre objet possède une longue liste d'attributs?

Aucun paramètre de méthode ne doit jamais être consommé sans validation. C'est donc paresse d'écrire:

setMyField(int myField){
    this.myField = myField;
}

Si vous l'écrivez de cette façon, vous avez au moins préparé l'utilisation du setter pour la validation; c'est mieux qu'un domaine public - mais à peine. Mais au moins vous avez une interface publique cohérente que vous pouvez revenir et mettre en place des règles de validation sans casser le code de vos clients.

Getters, setters, propriétés, mutators, appelez-les comme vous voulez, mais ils sont nécessaires.

0
Dale

Mon approche est la suivante -

Lorsque je m'attends à traiter les données plus tard, un getter/setter est justifié. De plus, si un changement se produit, je pousse souvent les données dans un getter/setter.

S'il s'agit d'une structure POD, je laisse les emplacements disponibles.

À un niveau plus abstrait, la question est "qui gère les données", et cela dépend du projet.

0
Paul Nathan

Si les getters et les setters violent l'encapsulation et le vrai OO, alors je suis sérieusement en difficulté.

J'ai toujours senti qu'un objet représente tout ce qui me vient à l'esprit que vous en avez besoin.

Je viens de terminer l'écriture d'un programme qui génère des labyrinthes en Java, et j'ai une classe qui représente "Maze Squares". J'ai des données dans cette classe qui représentent des coordonnées, des murs et des booléens, etc.

Je dois avoir un moyen de changer/manipuler/accéder à ces données! Que dois-je faire sans getters et setters? Java n'utilise pas de propriétés et définir toutes mes données locales à cette classe sur public est DEFINITIVEMENT une violation d'encapsulation et OO.

0
Bryan Harrington

Si l'utilisation des getters et setters semble compliquée, le problème pourrait être la langue, pas le concept lui-même.

Voici le code de l'exemple second écrit en Ruby:

class Fridge
  attr_accessor :cheese
end

def go_shopping fridge
  fridge.cheese += 5
end

Remarquez que cela ressemble beaucoup à l'exemple premier en Java? Lorsque les getters et setters sont traités comme des citoyens de première classe, ils ne sont pas une corvée à utiliser, et la flexibilité supplémentaire peut parfois être une véritable aubaine - par exemple, nous pourrions décider de renvoyer une valeur par défaut pour le fromage sur un nouveau réfrigérateur:

class Fridge
  attr_accessor :cheese

  def cheese
    @cheese || 0
  end
end

Bien sûr, il y aura de nombreuses variables qui ne devraient pas du tout être exposées publiquement. Exposer inutilement des variables internes aggravera votre code, mais vous pouvez difficilement en blâmer les getters et les setters.

0
Tobias Cohen