Je me retrouve souvent du mal à décider laquelle de ces deux façons d'utiliser lorsque j'ai besoin d'utiliser des données communes à travers certaines méthodes dans 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, et également pour éviter de définir des paramètres de méthode, mais il n'est peut-être pas aussi clair 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 qu'ils sont tous les deux corrects, lequel est vu/utilisé le plus souvent? De plus, si vous pouvez fournir des avantages/inconvénients supplémentaires pour chaque option, ce serait très utile.
Je suis surpris que cela n'ait pas encore été mentionné ...
Cela dépend si var1
fait en fait partie de l'état de votre objet .
Vous supposez que ces deux approches sont correctes et que c'est juste une question de style. Vous avez tort.
Il s'agit entièrement de savoir comment modéliser correctement.
De même, les méthodes d'instance private
existent pour muter l'état de votre objet. Si ce n'est pas ce que fait votre méthode, ce devrait être private static
.
Je ne sais pas ce qui est le plus répandu, mais je ferais toujours ce dernier. Il communique plus clairement le flux de données et la durée de vie, et il ne gonfle 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 juste déroutant et rend la révision du code beaucoup plus difficile, car je dois considérer la possibilité que n'importe quelle méthode puisse modifier var1
.
Vous devez réduire le scope 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, c'est OK de l'utiliser dans cette portée, c'est-à-dire l'objet entier. Dans ce cas, optez pour la première option. Si non, optez pour la deuxième option car elle réduit la visibilité des variables et donc la complexité globale.
Quel style est meilleur (variable d'instance vs valeur de retour) en Java
Il y a un autre style - utilisez 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 donne beaucoup de marge de manœuvre pour l'avenir.
Il s'agit d'un modèle qui fonctionne également bien dans un système distribué/serveur où certains détails doivent être conservés lors des appels. Vous pouvez stocker les détails de l'utilisateur, les connexions à la base de données, etc. dans l'objet state
.
Il s'agit d'effets secondaires.
Demander si var1
fait partie de l'Etat manque le point de cette question. Sûr var1
doit persister, il doit s'agir d'une instance. L'une ou l'autre approche peut être mise en œuvre, que la persévérance soit nécessaire ou non.
L'approche des effets secondaires
Certaines variables d'instance ne sont utilisées que pour communiquer entre les méthodes privées d'un appel à l'autre. Ce type de variable d'instance peut être refactorisé pour ne plus exister. Parfois, les choses sont plus claires avec eux. Mais cela n'est pas sans risque.
Vous laissez une variable hors de sa portée car elle est utilisée dans deux portées privées différentes. Pas parce que c'est nécessaire dans la portée que vous placez. Cela peut être déroutant. Les "globaux sont mauvais!" niveau de confusion. Cela peut fonctionner, mais il ne sera tout simplement pas bien adapté. Cela ne fonctionne que dans les petits. Pas de gros objets. Pas de longues chaînes d'héritage. Ne provoque pas un effet yo yo .
L'approche fonctionnelle
Maintenant, même si var1
doit persister rien ne dit que vous devez utiliser si, pour chaque valeur transitoire, il peut prendre avant d'atteindre l'état que vous souhaitez conserver entre les appels publics. Cela signifie que vous pouvez toujours définir un var1
instance utilisant uniquement des méthodes plus fonctionnelles.
Dans ces exemples, 'var1' n'est donc encapsulé que si votre débogueur sait qu'il existe. Je suppose que vous l'avez fait 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é sous l'héritage misérable yo yo qui mute une variable d'instance à plusieurs niveaux dans plusieurs méthodes et est allé écureuil en essayant de le suivre. Voilà le risque.
C'est la douleur qui m'amène à une approche plus fonctionnelle. Une méthode peut documenter ses dépendances et sa sortie dans sa signature. Il s'agit d'une approche puissante et claire. Il vous permet également de modifier ce que vous passez la méthode privée, ce qui la rend 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 d'objet est la capacité de se référer à un contexte en dehors de la méthode. Faire cela sans fuir les globaux partout ici et disparaître est la force de la POO. J'obtiens la flexibilité d'un global mais il 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 le fais, je suis obligé de donner au moins à la méthode un nom qui précise ce qu'elle fait pour que les gens ne soient pas surpris quand cela se produira. Les commentaires peuvent également aider. Parfois, ces commentaires sont formalisés comme des "conditions de publication".
L'inconvénient des méthodes privées fonctionnelles
L'approche fonctionnelle rend les dépendances certains claires. À moins que vous ne soyez dans un langage fonctionnel pur, il ne peut pas exclure les dépendances cachées. Vous ne savez pas, en regardant simplement une signature de méthode, qu'elle ne vous cache pas un effet secondaire dans le reste de son code. Ce n'est tout simplement pas le cas.
Post conditions
Si vous, et tous les autres membres de l'équipe, documentez de manière fiable les effets secondaires (conditions pré/post) dans les commentaires, alors le gain de l'approche fonctionnelle est beaucoup moins. Ouais je sais, rêve.
Conclusion
Personnellement, je tends vers des méthodes privées fonctionnelles dans les deux cas si je le peux, mais honnêtement, c'est principalement parce que ces commentaires d'effets secondaires pré/post conditionnels ne provoquent pas d'erreurs de compilation lorsqu'ils sont obsolètes ou lorsque les méthodes sont appelées hors service. À moins d'avoir vraiment besoin de la flexibilité des effets secondaires, je préfère simplement savoir que les choses fonctionnent.
La première variante me semble non intuitive et potentiellement dangereuse (imaginez pour une raison quelconque que quelqu'un rend votre méthode privée publique).
Je préfère 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.
Essayons un exemple qui fait quelque chose. Pardonnez-moi car c'est du javascript et non du Java. Le point devrait être le même.
Visitez https://blockly-games.appspot.com/pond-duck?lang=en , 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 devriez remarquer que dir
, wid
et dis
ne se transmettent pas beaucoup. Vous devez également remarquer que le code de la fonction que lock()
renvoie ressemble beaucoup à un pseudo-code. Eh bien, c'est du code réel. Pourtant, c'est très facile à lire. Nous pourrions ajouter des passes et des affectations, mais cela ajouterait de l'encombrement que vous ne verriez jamais dans un pseudo-code.
Si vous souhaitez faire valoir que ne pas faire l'attribution et le passage est bien parce que ces trois variables sont un état persistant, alors envisagez une refonte qui attribue à dir
une valeur aléatoire à chaque boucle. Pas persistant maintenant, n'est-ce pas?
Bien sûr, nous pourrions maintenant supprimer dir
dans la portée, mais pas sans être obligés d'encombrer notre pseudo-code avec le passage et la définition.
Donc non, ce n'est pas pourquoi vous décidez d'utiliser ou non les effets secondaires plutôt que de passer et de revenir. Les effets secondaires ne signifient pas à eux seuls 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éables à 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.
Il existe déjà des réponses concernant l'état de l'objet et le moment où la deuxième méthode est préférée. Je veux juste ajouter un cas d'utilisation courant pour le premier modèle.
Le premier modèle est parfaitement valide lorsque tout ce que fait votre classe est qu'elle encapsule un algorithme. Un cas d'utilisation est que si vous écriviez l'algorithme dans une seule méthode, il serait trop volumineux. Donc, vous le décomposez en méthodes plus petites, faites-en une classe et rendez les sous-méthodes privées.
Maintenant, passer tout l'état 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 vous mordre.