1
votes

Les getters retournant des objets ne permettent-ils pas aux appelants d'accéder directement aux variables membres?

Disons que j'ai une classe simple qui stocke les amis d'un utilisateur dans une ArrayList de chaînes, avec un getter pour accéder à cette ArrayList:

public class User
{
  private ArrayList<String> mFriends;
  
  // ...other code like constructors and setters...

  public ArrayList<String> getFriends()
  {
    return mFriends;
  }
}

Puisque Java et de nombreux autres langages le sont (de manière équivalente) pass-by-reference, cela ne permet pas à l'appelant de getFriends () d'accéder directement à mon ArrayList de chaînes c'est-à-dire qu'ils pourraient le modifier sans même appeler mon setter?

Je sens cela rompt énormément le concept d'encapsulation. Existe-t-il une solution de contournement normale pour cela, en plus de créer dynamiquement une nouvelle ArrayList avec les mêmes valeurs et de renvoyer cela, ou est-ce que je ne comprends pas quelque chose?

Edit: Je comprends que Java n'est pas vraiment pass-by-reference mais passe plutôt une copie de l'adresse de l'objet d'origine, mais cela a le même effet que le passage par référence lors de l'exposition d'objets à ceux qui sont en dehors de votre classe


5 commentaires

Ce contre quoi vous vous protégez ici, c'est le code extérieur qui modifie à quel ArrayList mFriends fait référence. Mais comme vous l'avez noté, il est toujours possible pour du code extérieur de modifier le contenu de cette liste. Si vous voulez vous protéger contre cela, vous pouvez peut-être utiliser Collections.unmodifiableList , ou renvoyer une copie.


Oui, c'est exactement ce que je veux dire. Comment cela fonctionnerait-il en ce qui concerne le renvoi d'objets Java "standard" non intégrés? Dois-je simplement faire confiance à l'utilisateur pour qu'il ne modifie pas son contenu sans utiliser mon setter, par exemple?


S'il est important qu'ils ne modifient pas le contenu, vous devriez probablement leur empêcher de le faire. Si vous mettez quelque chose à la disposition des utilisateurs, il y a un risque assez élevé que quelqu'un l'utilise indépendamment de ce que vous recommandez.


La reponse courte est oui'. Mais vous avez la responsabilité en tant que concepteur de ne pas exposer l'implémentation interne de votre classe au monde extérieur. Rendre mFriends privé puis fournir une méthode getter n'est pas différent de le rendre public. Considérez plutôt return Collections.unmodifiableList (mFriends) .


Vous savez peut-être que des articles comme Pourquoi les méthodes getter et setter sont mauvaises circulent depuis des décennies.


3 Réponses :


1
votes

Si vous vous inquiétez de l'encapsulation, vous pouvez renvoyer une copie de votre liste, par exemple

public ArrayList<String> getFriends() {
    return new ArrayList<>(mFriends);
  }

Au fait, Java n'est pas vraiment pass-by-reference mais plutôt pass-by-value .


2 commentaires

Oui désolé, j'ai édité mon passé concernant le commentaire de passage par référence. Cependant, je ne peux pas croire que créer une nouvelle copie à chaque fois serait performant, comme je l'ai spécifiquement dit dans mes commentaires "en plus de créer dynamiquement un nouveau ArrayList"


@GaryAllen C'est comme ça. Et les performances devraient être le cadet de vos soucis dans ce cas.



7
votes
  1. Java n'est pas , et n'a jamais implémenté le mécanisme de Pass by Reference , il a toujours été Pass by Value ;
  2. Le problème que vous décrivez est appelé Reference Escape , et oui, vous avez raison, l'appelant peut modifier votre objet, si vous l'exposez via référence ;
  3. Pour éviter le problème Reference Escape , vous pouvez soit:
    1. renvoie une copie complète de l'objet (avec .clone () );
    2. créer un nouvel objet avec les données existantes (par exemple new ArrayList <> (yourObjectHere) );
    3. ou trouvez une autre idée, il existe d'autres moyens de le faire;
  4. Cela ne rompt pas vraiment l'encapsulation en soi , c'est plutôt un point de conception correcte de la façon dont vous implémentez l'encapsulation;
  5. Votre préoccupation concernant les performances : non, cela ne nuira pas aux performances, il s'agit plutôt d'une conception appropriée de la POO, rendant obligatoire la mutabilité ou immuabilité de l'objet. Si vous deviez toujours renvoyer une copie complète au lieu d'une référence, vous n'auriez pas une chance d'avoir un bon effet de levier de votre objet. Prenons votre exemple: que se passe-t-il si vous voulez changer l'état de l'objet sans simplement définir un nouvel objet via son setter? et si vous souhaitez modifier les amis existants ( qui est dans votre exemple )? pensez-vous qu'il est préférable de créer une nouvelle liste d'amis et de la placer dans l'objet? non, vous perdez simplement le contrôle de votre objet dans ce dernier cas.

1 commentaires

Merci, le commentaire Reference Escape était principalement ce que je recherchais. Cette vidéo m'aide aussi beaucoup. Appréciez-le. (vidéo: youtube.com/watch?v=nW48K2a6t7k )



1
votes

Vous avez raison pour les objets mutables . Vous pouvez envelopper le champ avec Collections.unmodifiableList et autres. Ce que l'on voit aussi, c'est que les collections (mutables) n'ont tout simplement pas de getters, mais addFriend , getFriend (index) et autres.

En fait, les getters (et en particulier les setters) ne sont plus un modèle très estimé.

Le préfixe du membre "m" est à mon avis mieux adapté aux autres langues.


2 commentaires

Malgré le fait que votre réponse indique le comportement correct, je pense que c'est un peu trompeur en termes de sémantique de ce que la question pose. C'est le problème de l'échappement des références.


@GiorgiTsiklauri En effet, j'ai même voté pour votre réponse. Le problème avec la POO est que son concept de base (historique) est celui d'un état interne. Pouvez-vous atteindre via des méthodes d'accès un objet mutable, alors cela devrait être un comportement prévu . J'ai principalement répondu, car laisser les getters renvoyer une copie me paraissait une technique plutôt faible.