Quel style est le meilleur (variable d'instance et valeur de retour) en Java

32

Il m'est souvent difficile de décider laquelle de ces deux manières d'utiliser lorsque j'ai besoin d'utiliser des données communes pour certaines méthodes de mes classes. Quel serait un meilleur choix?

Dans cette option, je peux créer une variable d'instance pour éviter d'avoir à déclarer des variables supplémentaires, ainsi que pour définir les paramètres de la méthode, mais il est peut-être difficile de savoir où ces variables sont instanciées / modifiées:

public class MyClass {
    private int var1;

    MyClass(){
        doSomething();
        doSomethingElse();
        doMoreStuff();
    }

    private void doSomething(){
        var1 = 2;
    }

    private void doSomethingElse(){
        int var2 = var1 + 1;
    }

    private void doMoreStuff(){
        int var3 = var1 - 1;
    }
}

Ou simplement instancier des variables locales et les passer comme arguments?

public class MyClass {  
    MyClass(){
        int var1 = doSomething();
        doSomethingElse(var1);
        doMoreStuff(var1);
    }

    private int doSomething(){
        int var = 2;
        return var;
    }

    private void doSomethingElse(int var){
        int var2 = var + 1;
    }

    private void doMoreStuff(int var){
        int var3 = var - 1;
    }
}

Si la réponse est que les deux sont corrects, lequel est le plus souvent vu / utilisé? En outre, si vous pouvez fournir des avantages / inconvénients supplémentaires pour chaque option serait très utile.

Carlossierra
la source
1
Relatad (duplicate?): Programmers.stackexchange.com/questions/164347/…
Martin Ba
1
Je pense que personne n’a encore fait remarquer que le fait de placer des résultats intermédiaires dans des variables d’instance peut rendre la concurrence plus difficile, en raison de la possibilité de conflits entre les threads pour ces variables.
Sdenham

Réponses:

107

Je suis surpris que cela n'ait pas encore été mentionné ...

Cela dépend si var1fait réellement partie de l' état de votre objet .

Vous supposez que ces deux approches sont correctes et qu'il s'agit simplement d'une question de style. Vous avez tort.

C'est entièrement sur la façon de modéliser correctement.

De même, il privateexiste des méthodes d'instance pour muter l'état de votre objet . Si ce n'est pas ce que fait votre méthode, alors elle devrait l'être private static.

MetaFight
la source
7
@mucaho: transientn'a rien à voir avec cela, car il transients'agit d' un état persistant , comme dans certaines parties de l'objet qui sont enregistrées lorsque vous effectuez une opération telle que la sérialisation de l'objet. Par exemple, le magasin de sauvegarde d'un ArrayList est transientmême crucial pour l'état de ArrayList, car lorsque vous sérialisez un ArrayList, vous ne voulez enregistrer que la partie du magasin de sauvegarde contenant les éléments ArrayList réels, et non l'espace libre. à la fin réservé à d'autres ajouts d'élément.
user2357112 prend en charge Monica le
4
Ajout à la réponse - si cela var1est nécessaire pour quelques méthodes mais ne fait pas partie de l'état de MyClass, il peut être temps de placer var1ces méthodes dans une autre classe à utiliser par MyClass.
Mike Partridge
5
@CandiedOrange Nous parlons de modélisation correcte ici. Lorsque nous disons "partie de l'état de l'objet", nous ne parlons pas des éléments littéraux du code. Nous discutons d'une notion conceptuelle d'état, quel devrait être l'état de l'objet pour modéliser correctement les concepts. La "durée de vie utile" est probablement le facteur décisif le plus déterminant à cet égard.
JPMc26
4
@CandiedOrange Next et Previous sont évidemment des méthodes publiques. Si la valeur de var1 n'est pertinente que pour une séquence de méthodes privées, il ne fait clairement pas partie de l'état persistant de l'objet et doit donc être transmise en tant qu'argument.
Taemyr
2
@CandiedOrange Vous avez précisé ce que vous vouliez dire après deux commentaires. Je dis que vous pourriez facilement avoir dans la première réponse. De plus, oui, j'ai oublié qu'il n'y a pas que l'objet; Je conviens que parfois les méthodes d'instance privée ont un but. Je ne pouvais tout simplement rien en dire. EDIT: Je viens de réaliser que vous n'étiez pas, en fait, la première personne à me répondre. Ma faute. J'étais confus quant aux raisons pour lesquelles une réponse était une réponse brève, une phrase, sans réponse, alors que la suivante expliquait réellement les choses.
Le procès de Monica
17

Je ne sais pas ce qui est le plus répandu, mais je ferais toujours le dernier. Il communique plus clairement le flux de données et la durée de vie, et il ne bloque pas chaque instance de votre classe avec un champ dont la seule durée de vie pertinente est pendant l'initialisation. Je dirais que le premier est source de confusion et rend la révision de code beaucoup plus difficile, car je dois envisager la possibilité que toute méthode puisse être modifiée var1.

Derek Elkins
la source
7

Vous devriez réduire la portée de vos variables autant que possible (et raisonnable). Non seulement dans les méthodes, mais en général.

Pour votre question, cela signifie que cela dépend si la variable fait partie de l'état de l'objet. Si oui, il est correct de l'utiliser dans cette étendue, c'est-à-dire tout l'objet. Dans ce cas, choisissez la première option. Si non, choisissez la deuxième option car elle réduit la visibilité des variables et donc la complexité globale.

Andy
la source
5

Quel style est le meilleur (variable d'instance et valeur de retour) en Java

Il y a un autre style - utiliser un contexte / état.

public static class MyClass {
    // Hold my state from one call to the next.
    public static final class State {
        int var1;
    }

    MyClass() {
        State state = new State();
        doSomething(state);
        doSomethingElse(state);
        doMoreStuff(state);
    }

    private void doSomething(State state) {
        state.var1 = 2;
    }

    private void doSomethingElse(State state) {
        int var2 = state.var1 + 1;
    }

    private void doMoreStuff(State state) {
        int var3 = state.var1 - 1;
    }
}

Cette approche présente plusieurs avantages. Les objets d'état peuvent changer indépendamment de l'objet, par exemple, ce qui laisse beaucoup de marge de manœuvre pour l'avenir.

C'est un modèle qui fonctionne également bien dans un système distribué / serveur où certains détails doivent être préservés lors d'appels. Vous pouvez stocker les détails de l'utilisateur, les connexions à la base de données, etc. dans l' stateobjet.

Vieux curcuma
la source
3

Il s'agit d'effets secondaires.

Demander si cela var1fait partie de l'État passe à côté de l'essentiel de cette question. Bien sûr, si var1doit persister, il doit s'agir d'une instance. Quelle que soit l'approche choisie, que la persistance soit nécessaire ou non.

L'approche des effets secondaires

Certaines variables d'instance ne sont utilisées que pour communiquer entre méthodes privées d'un appel à l'autre. Ce type de variable d'instance peut être remanié en dehors de l'existence, mais ce n'est pas obligatoire. Parfois, les choses sont plus claires avec eux. Mais ce n'est pas sans risque.

Vous laissez une variable hors de sa portée car elle est utilisée dans deux étendues privées différentes. Pas parce que c'est nécessaire dans le champ d'application que vous placez. Cela peut être déroutant. Les "globals sont diaboliques!" niveau de confusion. Cela peut marcher mais ça ne va pas bien. Cela ne fonctionne que dans le petit. Pas de gros objets. Pas de longues chaînes d'héritage. Ne cause pas d' effet yo yo .

L'approche fonctionnelle

Maintenant, même si var1doit persister, rien ne dit que vous devez utiliser si, pour chaque valeur transitoire, elle peut prendre avant d’atteindre l’état que vous souhaitez préserver entre les appels publics. Cela signifie que vous pouvez toujours définir une var1instance en utilisant uniquement des méthodes plus fonctionnelles.

Donc, qu’il fasse partie de l’État ou non, vous pouvez toujours utiliser l’une ou l’autre approche.

Dans ces exemples, 'var1' n'est donc pas encapsulé, à part que le débogueur le sache. Je suppose que vous avez fait cela délibérément parce que vous ne voulez pas nous biaiser. Heureusement, je m'en fiche.

Le risque d'effets secondaires

Cela dit, je sais d'où vient votre question. J'ai travaillé avec un héritage misérable de votre part qui mute une variable d'instance à plusieurs niveaux en plusieurs méthodes et qui est parti à la louche en essayant de la suivre. C'est le risque.

C'est la douleur qui me pousse vers une approche plus fonctionnelle. Une méthode peut documenter ses dépendances et la sortie dans sa signature. C'est une approche puissante et claire. Cela vous permet également de changer ce que vous passez avec la méthode privée pour la rendre plus réutilisable dans la classe.

L'avantage des effets secondaires

C'est aussi limitant. Les fonctions pures n'ont pas d'effets secondaires. Cela peut être une bonne chose mais ce n’est pas orienté objet. Une grande partie de l'orientation des objets est la possibilité de faire référence à un contexte extérieur à la méthode. Le fait de ne pas laisser échapper de globales partout ici et disparaître est la force de la POO. Je reçois la flexibilité d’un global mais c’est bien contenu dans la classe. Je peux appeler une méthode et muter chaque variable d'instance à la fois si je le souhaite. Si je fais cela, je suis obligé au moins de donner à la méthode un nom qui précise ce que c'est que faire pour que les gens ne soient pas surpris quand cela se produit. Les commentaires peuvent aussi aider. Parfois, ces commentaires sont formalisés en tant que "conditions post".

Les inconvénients des méthodes privées fonctionnelles

L'approche fonctionnelle clarifie certaines dépendances. Sauf dans un langage purement fonctionnel, il ne peut pas exclure les dépendances cachées. Vous ne savez pas, en regardant simplement une signature de méthode, que cela ne vous cache pas un effet secondaire dans le reste de son code. Tu ne le fais pas.

Post conditionals

Si vous et tous les autres membres de l’équipe documentez de manière fiable les effets secondaires (conditions pré / post) dans les commentaires, le gain de l’approche fonctionnelle sera bien moindre. Ouais je sais, rêve sur.

Conclusion

Personnellement, dans les deux cas, j'ai tendance à utiliser des méthodes privées fonctionnelles, mais honnêtement, c'est surtout parce que ces commentaires sur les effets secondaires pré / post conditionnels ne causent pas d'erreurs de compilation lorsqu'ils sont obsolètes ou lorsque des méthodes sont appelées en panne. Sauf si j'ai vraiment besoin de la flexibilité des effets secondaires, je préfère simplement savoir que les choses fonctionnent.

MagicWindow
la source
1
"Certaines variables d'instance ne sont utilisées que pour communiquer entre méthodes privées d'un appel à l'autre." C'est une odeur de code. Lorsque les variables d'instance sont utilisées de cette manière, c'est un signe que la classe est trop grande. Extrayez ces variables et méthodes dans une nouvelle classe.
Kevin Cline
2
Cela n'a vraiment aucun sens. Bien que le PO puisse écrire le code dans le paradigme fonctionnel (et ce serait généralement une bonne chose), ce n'est évidemment pas le contexte de la question. Essayer de dire aux OP qu'ils peuvent éviter de stocker l'état de l'objet en changeant de paradigmes n'a pas vraiment d'importance ...
jpmc26
@kevincline l'exemple de l'OP n'a qu'une variable d'instance et 3 méthodes. Essentiellement, il a déjà été extrait dans une nouvelle classe. Non pas que l'exemple fasse quelque chose d'utile. La taille des classes n'a rien à voir avec la façon dont vos méthodes d'assistance privée communiquent entre elles.
MagicWindow
@ jpmc26 OP a déjà montré qu’ils pouvaient éviter de stocker en var1tant que variable d’état en modifiant les paradigmes. La portée de la classe n'est PAS là où l'état est stocké. C'est aussi une portée englobante. Cela signifie qu'il y a deux motivations possibles pour mettre une variable au niveau de la classe. Vous pouvez prétendre le faire uniquement pour le champ d’application englobant et non pour dire que c’est le mal, mais j’affirme que c’est un compromis avec l’ arité qui est également pervers. Je le sais parce que j'ai dû maintenir un code qui le fait. Certains ont été bien fait. Certains étaient un cauchemar. La ligne entre les deux n'est pas l'état. C'est la lisibilité.
MagicWindow
La règle d'état améliore la lisibilité: 1) évitez que les résultats intermédiaires des opérations ressemblent à l'état 2) évitez de masquer les dépendances des méthodes. Si l’arité d’une méthode est élevée, elle représente soit de manière irréductible et réelle la complexité de cette méthode, soit la conception actuelle présente une complexité inutile.
Sdenham
1

La première variante semble non intuitive et potentiellement dangereuse pour moi (imaginez, pour une raison quelconque, que quelqu'un rende publique votre méthode privée).

Je préférerais de beaucoup instancier vos variables lors de la construction de la classe ou les transmettre comme arguments. Ce dernier vous donne la possibilité d'utiliser des idiomes fonctionnels et de ne pas vous fier à l'état de l'objet contenant.

Brian Agnew
la source
0

Il existe déjà des réponses sur l'état d'objet et sur le choix de la seconde méthode. Je veux juste ajouter un cas d'utilisation courant pour le premier motif.

Le premier modèle est parfaitement valide lorsque votre classe ne fait qu’encapsuler un algorithme . Un cas d’utilisation pour ceci est que si vous écriviez l’algorithme sur une seule méthode, il serait trop volumineux. Donc, vous le divisez en méthodes plus petites, faites-en une classe et rendez les sous-méthodes privées.

Maintenant, passer tous les états de l'algorithme via des paramètres peut devenir fastidieux, vous devez donc utiliser les champs privés. Il est également en accord avec la règle du premier paragraphe car il s’agit essentiellement d’un état de l’instance. Vous devez seulement garder à l'esprit et documenter correctement que l'algorithme n'est pas réentrant si vous utilisez des champs privés pour cela . Cela ne devrait pas être un problème la plupart du temps, mais cela peut éventuellement vous mordre.

Honza Brabec
la source
0

Essayons un exemple qui fait quelque chose. Pardonnez-moi, javascript pas java. Le point devrait être le même.

Visitez https://blockly-games.appspot.com/pond-duck?lang=fr , cliquez sur l'onglet javascript et collez-le:

hunt = lock(-90,1), 
heading = -135;

while(true) {
  hunt()  
  heading += 2
  swim(heading,30)
}

function lock(direction, width) {
  var dir = direction
  var wid = width
  var dis = 10000

  //hunt
  return function() {
    //randomize() //Calling this here makes the state of dir meaningless
    scanLock()
    adjustWid()
    if (isSpotted()) {
      if (inRange()) {
        if (wid <= 4) {
          cannon(dir, dis)
        }
      }
    } else {
      if (!left()) {
        right()
      }
    }
  }

  function scanLock() {
    dis = scan(dir, wid);
  }

  function adjustWid() {
    if (inRange()) {
      if (wid > 1)
        wid /= 2;
    } else {
      if (wid < 16) {
        wid *= 2; 
      }
    }
  }

  function isSpotted() {
    return dis < 1000;
  }

  function left() {
    dir += wid;
    scanLock();
    return isSpotted();
  }

  function right() {
    dir -= wid*2;
    scanLock();
    return isSpotted()
  }

  function inRange() {
    return dis < 70;
  }

  function randomize() {
    dir = Math.random() * 360
  }
}

Vous remarquerez que dir, widet disne se passé autour d' un lot. Vous devriez également remarquer que le code dans la fonction qui lock()retourne ressemble beaucoup à du pseudo-code. Eh bien, c'est le code réel. Pourtant, c'est très facile à lire. Nous pourrions ajouter des passes et des assignations, mais cela ajouterait un fouillis que vous ne verriez jamais dans un pseudo-code.

Si vous souhaitez faire valoir que ne pas effectuer l'assignation et le passage est correct car ces trois vars sont persistants, envisagez une refonte qui attribue dirune valeur aléatoire à chaque boucle. Pas persistant maintenant, est-ce?

Bien sûr, nous pourrions maintenant nous écarter dirde la portée, mais non sans être obligés d'encombrer notre pseudo-code, tel que le code, le passage et le réglage.

Donc non, l’Etat n’est pas la raison pour laquelle vous décidez d’utiliser ou non des effets secondaires plutôt que de passer et de revenir. Les effets secondaires ne signifient pas non plus que votre code est illisible. Vous n'obtenez pas les avantages du code fonctionnel pur . Mais bien fait, avec de bons noms, ils peuvent en fait être agréable à lire.

Cela ne veut pas dire qu'ils ne peuvent pas se transformer en spaghetti comme un cauchemar. Mais alors, qu'est-ce qui ne peut pas?

Bonne chasse au canard.

confits_orange
la source
1
Les fonctions internes / externes sont un paradigme différent de celui décrit par OP. - Je conviens que le compromis entre les variables locales dans une fonction externe et les arguments passés aux fonctions internes est similaire à celui des variables privées dans une classe contre les arguments passés à des fonctions privées. Toutefois, si vous effectuez cette comparaison, la durée de vie de l'objet correspond à une seule exécution de la fonction. Par conséquent, les variables de la fonction externe font partie de l'état persistant.
Taemyr
@Taemyr the OP décrit une méthode privée appelée au sein d'un constructeur public et qui ressemble beaucoup à la méthode inner au sein de l'utilisateur. L'état n'est pas vraiment "persistant" si vous y appuyez à chaque fois que vous entrez dans une fonction. Parfois, l'état est utilisé comme un lieu partagé. Les méthodes peuvent écrire et lire. Cela ne signifie pas qu'il doit être persisté. Le fait qu'il persiste de toute façon n'est pas une chose sur laquelle il faut compter.
candied_orange
1
C'est persistant même si vous marchez dessus à chaque fois que vous disposez de l'objet.
Taemyr
1
Bons points, mais les exprimer en JavaScript obscurcit vraiment la discussion. De plus, si vous supprimez la syntaxe JS, vous obtenez l'objet de contexte (la fermeture de la fonction extérieure)
fdreger
1
@sdenham J'affirme qu'il existe une différence entre les attributs d'une classe et les résultats intermédiaires transitoires d'une opération. Ils ne sont pas équivalents. Mais l'un peut être stocké dans l'autre. Ce n'est certainement pas nécessaire. La question est de savoir si cela devrait jamais l'être. Oui, il y a des problèmes de sémantique et de durée de vie. Il y a aussi des problèmes d'arité. Vous ne pensez pas qu'ils sont assez importants. C'est très bien. Mais dire que l'utilisation du niveau de classe pour autre chose que l'état d'objet est une modélisation incorrecte, c'est insister sur un mode de modélisation. J'ai maintenu le code professionnel qui l'a fait dans l'autre sens.
candied_orange