Les objets en programmation orientée objet sont Il existe plusieurs méthodes de gestion de l'état interne en fonction de la méthode de mise en œuvre.
En comprenant correctement cette méthode de gestion d'état interne Les bogues sont difficiles à se produire, et même s'ils se produisent, la cause peut être facilement identifiée. Vous pourrez également créer des programmes faciles à tester.
Dans cet article, j'expliquerai l'immuabilité de l'état interne. Il aborde également brièvement les effets secondaires de la méthode.
――Je voudrais dire que orienté objet en général ... mais les gens qui utilisent le langage Java --Pour la programmation jusqu'à la 2e année
Dans cette explication, le code est créé en utilisant "Janken" comme exemple. Les mains Janken (stratégies) et les arbitres suivants sont définis à l'avance. (Le jugement de Janken n'a rien à voir avec ce sujet, je suis donc désolé pour la mise en œuvre approximative)
public enum HandStrategy {
/**Goo*/
ROCK,
/**Par*/
PAPER,
/**Choki*/
SCISSORS
}
public class Referee {
public static enum Result {
LEFT_WIN,
RIGHT_WIN,
DRAW
}
public static Result battle(HandStrategy left, HandStrategy right) {
if (left == right)
return Result.DRAW;
if (left == HandStrategy.ROCK)
return right == HandStrategy.PAPER ? Result.RIGHT_WIN : Result.LEFT_WIN;
else if (left == HandStrategy.PAPER)
return right == HandStrategy.SCISSORS ? Result.RIGHT_WIN : Result.LEFT_WIN;
else
return right == HandStrategy.ROCK ? Result.RIGHT_WIN : Result.LEFT_WIN;
}
}
L'état interne d'un objet est la valeur (ou une combinaison de plusieurs) des variables membres (également appelées champs d'instance en Java) que possède chaque objet instancié.
Par exemple, dans les cas suivants
public class Hand {
private HandStrategy strategy;
public void setStrategy(HandStrategy strategy) {
this.strategy = strategy;
}
public static void main(String[] args) {
Hand left = new Hand();
Hand right = new Hand();
left.setStrategy(HandStrategy.ROCK);
right.setStrategy(HandStrategy.PAPER);
}
}
L'état interne (stratégie
) de l'objet gauche
est goo ( ROCK
),
L'état interne («stratégie») de l'objet «droit» est par («PAPIER»).
Ce sera.
Le contenu de la variable n'est pas important pour le sujet «invariance» Il classe quand et comment le contenu change ou ne change pas.
Classifications invariantes "Mutable" et "Immuable", Et je vais vous expliquer un peu spécial "apatride".
Mutable signifie "variable" Un objet dont l'état interne peut changer au milieu.
public class MutableHand {
private HandStrategy strategy;
public void setStrategy(HandStrategy strategy) {
this.strategy = strategy;
}
public HandStrategy getStrategy() {
return strategy;
}
}
L'objet a un état (= il y a un champ d'instance), et il existe une méthode pour changer l'état. Cette fois, il existe une méthode pour réécrire intentionnellement l'état, Certaines méthodes peuvent être réécrites sans le vouloir. Plus d'informations à ce sujet dans le chapitre Immuable.
private static void execMutable() {
final MutableHand left = new MutableHand();
final MutableHand right = new MutableHand();
left.setStrategy(HandStrategy.ROCK);
right.setStrategy(HandStrategy.PAPER);
Referee.battle(left.getStrategy(), right.getStrategy());
//La deuxième fois que nous changeons de mains et jouons
left.setStrategy(HandStrategy.SCISSORS);
Referee.battle(left.getStrategy(), right.getStrategy());
}
La variable qui stocke l'objet est qualifiée «final», Il est important de noter que cela ne change pas et que l'état interne de l'objet peut être modifié.
Ne créez pas de nouvel objet lors de la lecture du deuxième jeu L'état de l'objet existant est réécrit et exécuté.
Si vous implémentez une classe sans une compréhension invariante, vous vous retrouverez probablement avec cette implémentation Mutable. C'est parce que c'est le plus simple à mettre en œuvre et qu'il y a peu de choses à considérer. D'autre part, l'implémentation Immutable, qui sera expliquée plus loin, a quelques considérations et est assez gênante.
En général, les implémentations de Mutable sont difficiles à écrire des tests, et lorsque des bogues sont introduits, ils ont tendance à être moins reproductibles et prennent plus de temps à identifier la cause. (Reportez-vous à la colonne des inconvénients) Vous ne devez donc pas choisir de manière agressive une implémentation Mutable. Cependant, il est sélectionné pour des raisons négatives, principalement lorsqu'il est difficile de rendre Immuable. (Pourquoi cela peut être difficile plus tard)
** Le montant de la mise en œuvre sera réduit ** Si vous souhaitez simplement stocker les données, vous pouvez les créer avec une implémentation minimale. (On peut se demander si c'est le meilleur)
** Peut être changé pour un autre état sans recréer l'instance ** En termes d'efficacité de la mémoire, il est efficace car il ne nécessite que de modifier les champs et consomme moins de mémoire supplémentaire. Cela peut également réduire le coût de l'instanciation, ce qui peut être utile dans les situations critiques en termes de performances.
** Les résultats de l'exécution de la méthode diffèrent selon l'état interne ** Puisque le résultat de l'exécution de la méthode peut dépendre de l'état interne, il peut ne pas être possible de le couvrir sans créer autant de cas de test qu'il y a de combinaisons d'états. Il peut ne pas être possible de couvrir le test, en particulier dans les situations où l'état interne change d'un moment à l'autre.
** Changement des résultats dans l'ordre de traitement ** Lors du test de plusieurs méthodes, si l'état interne de la méthode exécutée immédiatement avant le test change, cela affectera le test suivant. Comme on s'attend à ce que le traitement soit plus compliqué pendant la production, il est facile pour les bogues d'être mélangés s'ils sont affectés dans l'ordre.
** Il est nécessaire de savoir quand le statut a changé ** Si vous essayez de reproduire quand un bogue se produit, vous ne pouvez pas le reproduire à moins de savoir où et quel type d’état interne l’objet cause a changé, ou il est difficile de trouver «où l’état interne a changé» et d’enquêter Prend souvent du temps.
** Difficile de réutiliser des objets déjà utilisés ** S'il a déjà été utilisé (référencé), il est difficile de le réutiliser ailleurs. En effet, il existe un risque qu'un problème difficile à identifier se produise si l'état interne est réécrit par inadvertance.
Immuable signifie «immuable» Un objet dont l'état interne ne change pas une fois instancié.
public class ImmutableHand {
private final HandStrategy strategy;
public ImmutableHand(HandStrategy strategy) {
this.strategy = strategy;
}
public HandStrategy getStrategy() {
return strategy;
}
}
L'objet a un état, mais il n'y a pas de méthode pour changer l'état. L'état est déterminé par le constructeur lors de l'instanciation. Après cela, l'état ne doit pas être changé.
private static void execImmutable() {
final ImmutableHand left = new ImmutableHand(HandStrategy.ROCK);
final ImmutableHand right = new ImmutableHand(HandStrategy.PAPER);
Referee.battle(left.getStrategy(), right.getStrategy());
//Créer une nouvelle instance la deuxième fois
final ImmutableHand left2 = new ImmutableHand(HandStrategy.SCISSORS);
Referee.battle(left2.getStrategy(), right.getStrategy());
}
Après instanciation, l'objet ne peut pas changer son état interne, donc Si vous souhaitez changer de main une seconde fois, vous aurez besoin d'une nouvelle instanciation. Cependant, si vous n'avez pas à changer de main, il est garanti que vous pouvez réutiliser en toute sécurité l'objet précédemment utilisé. S'il est immuable, le contenu ne changera pas.
Les implémentations immuables sont généralement considérées comme supérieures aux implémentations mutables. L'implémentation Immutable peut résoudre certains des problèmes de testabilité et spécifiques aux bogues de l'implémentation Mutable. (Voir la colonne du mérite)
Vous pouvez transmettre des paramètres pour déterminer l'état interne lors de l'instanciation, mais vous ne pouvez pas modifier l'état interne à d'autres moments. Cela garantit que quoi que vous fassiez avec un objet déjà instancié, son état interne ne changera pas, c'est-à-dire que l'objet ne sera pas affecté avant ou après sa réutilisation.
L'implémentation Immutable sonne bien, mais Vous devez respecter des restrictions très strictes afin de profiter de ses avantages.
La première est que l'état interne doit être déterminé lors de l'instanciation comme décrit ci-dessus. L'autre est que les champs d'instance des objets immuables doivent également être immuables.
L'implémentation suivante est-elle immuable?
public class NonImmutable {
private final String[] word = {"pomme", "gorille", "rap"};
public String[] getWord() {
return word;
}
}
À première vue, il n'y a pas de méthode pour changer l'état interne. Cependant, lorsqu'il est utilisé comme suit,
private static void execNonImmutable() {
NonImmutable non = new NonImmutable();
non.getWord()[1] = "Godzilla";
System.out.println(non.getWord()[1]);
}
La chaîne est-elle affichée sur la console «Gorilla» ou «Godzilla»?
La bonne réponse est «Godzilla».
Cette implémentation n'est pas immuable car si vous laissez un champ Mutable hors de l'objet tel quel, il peut être modifié à l'extérieur.
Il convient de noter que l'état interne du champ d'instance ne peut pas être réécrit lors d'une implémentation immuable. (Il faut que tout soit invariant en remontant à l'état interne de l'état interne ...)
La bibliothèque Java possède certaines fonctionnalités qui la rendent immuable.
Par exemple
Collections.unmodifiableList(list)
Vous pouvez utiliser la méthode ci-dessus pour créer une liste immuable à partir d'une liste existante.
C'est une bonne idée d'utiliser ces fonctionnalités pratiques pour éviter les modifications accidentelles.
** Les résultats ne dépendent pas de l'ordre d'exécution de la méthode ** Même si vous testez plusieurs méthodes dans un test, elles ne sont pas affectées dans l'ordre.
** Facile à comprendre le statut ** Il est facile de comprendre l'état car seuls les paramètres au moment de l'instanciation affectent l'état interne.
** Objets faciles à réutiliser ** Il n'y a rien à considérer car il est garanti que l'état de l'objet ne sera pas affecté même s'il est réutilisé.
** L'efficacité de la mémoire peut être bonne ** L'efficacité de la mémoire peut être améliorée en créant les objets à utiliser à l'avance et en les implémentant afin qu'ils puissent être réutilisés. Cependant, c'est le cas lorsque le modèle d'états est limité et prévisible. (Si c'est imprévisible, ce sera un inconvénient)
** L'efficacité de la mémoire peut être médiocre ** Une fois instancié, l'état interne ne peut pas être modifié, vous devez donc créer un nouvel objet avec un état différent. Cela fonctionne contre de nombreux modèles de conditions.
** Les résultats de l'exécution de la méthode diffèrent selon l'état interne ** Puisque le résultat de l'exécution de la méthode peut dépendre de l'état interne, il peut ne pas être possible de le couvrir sans créer autant de cas de test qu'il y a de combinaisons d'états. Cependant, il est toujours plus facile à tester que Mutable.
** Les restrictions compliquent (ou impossibles) la mise en œuvre ** Les champs d'implémentation immuables ne peuvent pas être exposés au monde extérieur, ce qui peut forcer des implémentations complexes telles que la copie défensive. Immuable est difficile si l'état ne peut pas être spécifié au moment de l'instanciation.
Apatride signifie "n'a pas d'état" Cela signifie que l'objet n'a pas d'état, qu'il soit instancié ou non.
L'apatride est également immuable comme condition préalable. Immuable est classé comme immuable, mais comme il possède des fonctionnalités supplémentaires, il est intentionnellement défini comme une trame séparée.
public abstract class StatelessHand {
private static final Rock ROCK_IMPL = new Rock();
private static final Paper PAPER_IMPL = new Paper();
private static final Scissors SCISSORS_IMPL = new Scissors();
public static StatelessHand of(HandStrategy strategy) {
switch(strategy) {
case ROCK:
return ROCK_IMPL;
case PAPER:
return PAPER_IMPL;
case SCISSORS:
return SCISSORS_IMPL;
}
throw new IllegalArgumentException();
}
public abstract HandStrategy getStrategy();
public abstract String getName();
private static class Rock extends StatelessHand {
@Override
public HandStrategy getStrategy() {
return HandStrategy.ROCK;
}
@Override
public String getName() {
return "Goo";
}
}
private static class Paper extends StatelessHand {
@Override
public HandStrategy getStrategy() {
return HandStrategy.PAPER;
}
@Override
public String getName() {
return "Par";
}
}
private static class Scissors extends StatelessHand {
@Override
public HandStrategy getStrategy() {
return HandStrategy.SCISSORS;
}
@Override
public String getName() {
return "Choki";
}
}
}
L'objet n'a pas d'état. (Aucun champ d'instance)
Les méthodes de chaque classe d'implémentation (Rock
, Paper
, Scissors
) renvoient une valeur fixe. (= Le résultat ne change pas)
Un autre objet apatride est déjà apparu. C'est «arbitre».
private static void execStateless() {
final StatelessHand left = StatelessHand.of(HandStrategy.ROCK);
final StatelessHand right = StatelessHand.of(HandStrategy.PAPER);
System.out.println(left.getName() + " VS " + right.getName());
Referee.battle(left.getStrategy(), right.getStrategy());
//Obtenez une nouvelle instance la deuxième fois
final StatelessHand left2 = StatelessHand.of(HandStrategy.SCISSORS);
System.out.println(left2.getName() + " VS " + right.getName());
Referee.battle(left2.getStrategy(), right.getStrategy());
}
Les implémentations sans état ne nécessitent presque toujours pas d'instanciation.
Referee
exécute la méthode battle
sans l'instancier.
L'état de l'objet n'affecte pas le résultat (valeur de retour).
À première vue, l'implémentation Stateless ne semble pas avoir besoin d'être instanciée,
Les sous-classes de StatelessHand
sont instanciées.
C'est une méthode efficace lorsque vous souhaitez obtenir un polymorphisme même avec une implémentation sans état.
Les méthodes d'objet sans état ont le même résultat si les arguments sont les mêmes. Cependant, il ne semble pas donner le même résultat si vous utilisez des objets sans argument (par exemple statiques) dans la méthode. Ce n'est pas syntaxiquement un argument, mais il agit comme un "argument caché" pour exécuter la méthode. C'est un problème différent du fait que le résultat ne dépend pas de l'état de l'objet sans état.
Lors de l'implémentation de "strict Stateless [^ 1]" (seuls les arguments affectent le résultat), vous devez utiliser des objets immuables autres que les arguments.
La sous-classe de StatelessHand
est stricte Stateless car elle utilise les objets immuables ʻEnum et
String` dans ses méthodes.
[^ 1]: "Strict Stateless" est un mot que j'ai inventé sans permission, donc il ne peut pas être compris par d'autres personnes (transpiration)
System.lineSeparator ()
(renvoyant un code de saut de ligne).** L'efficacité de la mémoire est presque la plus élevée ** Une instance peut être la seule dans une application, car les objets sans état peuvent être réutilisés aussi souvent que vous le souhaitez. En fonction de votre implémentation, vous n'aurez peut-être même pas besoin de l'instancier.
** Les résultats ne dépendent pas de l'ordre d'exécution de la méthode ** Même si vous testez plusieurs méthodes dans un test, elles ne sont pas affectées dans l'ordre. (Il y a des remarques. Voir la colonne des effets secondaires)
** La méthode renvoie toujours le même résultat si les arguments sont les mêmes ** Il est souvent facile de créer des cas de test car le résultat d'une méthode dépend de l'argument dans n'importe quel état de l'application. (Cependant, vous devez faire attention aux "arguments cachés")
** Ne peut pas être encapsulé ** L'encapsulation (ici, signifiant combiner données et traitement), qui est une caractéristique et un avantage de la programmation orientée objet, ne peut pas être effectuée dans les implémentations sans état car il n'y a pas de champs d'instance.
** Il y a quelques tours (ressemble à) ** Il n'y a presque aucun tour autre que la classe utilitaire. Cependant, ce que vous définissez comme une classe signifie. En fait, les objets sans état sont fréquemment utilisés dans Java 8 et versions ultérieures.
Il fut un temps où les objets sans état étaient rarement utilisés dans la programmation orientée objet il y a dix ans, ou plutôt appelés anti-modèles. (Puisqu'il n'y a pas d'état, la méthode est définie statiquement sans l'instancier, etc.)
Cependant, l'introduction du savoir-faire en programmation fonctionnelle de manière orientée objet a donné à Stateless une position importante. Je ne couvrirai pas tout le savoir-faire qui vous a été apporté ici, mais un exemple est ** Lambda **.
Lambda en Java utilise en fait des objets avec une implémentation sans état.
L'exemple suivant est une méthode qui transmet a et b comme arguments et une expression lambda qui effectue un calcul sur ces deux valeurs.
calc(a, b, (x,y)->x+y)
Si vous appelez cette méthode sans utiliser lambda, elle ressemblera à ceci:
calc(a, b, new BiFunction<Integer, Integer, Integer>() {
@Override
public Integer apply(Integer x, Integer y) {
return x + y;
}
});
Nous instancions et passons une classe qui implémente une interface appelée «BiFunction». Lambda est une méthode de description qui simplifie au maximum l'implémentation et l'instanciation d'une classe, et l'objet est une implémentation sans état.
final int offset = /*Valeur de décalage*/;
calc(a, b, (x,y)->x+y+offset);
calc(a, b, new BiFunction<Integer, Integer, Integer>() {
@Override
public Integer apply(Integer x, Integer y) {
return x + y + offset;
}
});
Dans le cas d'une expression lambda, il est clair qu'un objet autre que l'argument est utilisé, donc je pense que la lisibilité est bonne.
Les références Lambda et de méthode apparues dans Java 8 (les détails sont omis) ont des caractéristiques grammaticales différentes de celles de Java traditionnel et sont difficiles à comprendre pour les débutants, mais Avec leur avènement, les apatrides en sont venus à trouver une valeur significative, c'est donc une bonne idée de le considérer sans penser qu'il n'a aucune utilité ou est un anti-modèle.
Il y a un «effet secondaire» qui est souvent utilisé (ou ressenti) dans la programmation fonctionnelle.
La méthode d'objet sans état reçoit un argument et renvoie le résultat en tant que valeur de retour, mais lors de la transmission de cette méthode, l'état interne de l'objet passé en tant qu'argument change, ou l'intérieur de «l'argument caché» expliqué ci-dessus Le changement de condition est appelé un «effet secondaire».
Lorsque cet effet secondaire se produit, il peut se comporter comme si l'avantage Stateless «Les méthodes renvoient toujours le même résultat» n'est pas suivi. En fait, il est exécuté avec l'état interne de l'argument modifié, donc ce ne sera pas "si l'argument est le même", mais il y a un risque de l'oublier et de provoquer un bogue difficile à reproduire.
Tous les effets secondaires ne sont pas mauvais. (Au moins dans la programmation orientée objet) Les implémentations immuables ne changent pas d'état, mais peuvent réécrire l'état de l'objet variable donné dans l'argument. (La méthode de paramétrage de l'état interne de l'objet passé en argument est une technique courante)
Lors de la mise en œuvre d'immuable et sans état, il est nécessaire de rendre la méthode exempte d'effets secondaires ou de spécifier les effets secondaires dans les commentaires.
Les implémentations mutables sont faciles à créer avec peu de restrictions. Si les avantages l'emportent sur les inconvénients, vous pouvez l'inclure dans les options, mais cela vous rendra heureux plus tard si vous considérez d'abord s'il peut être implémenté dans Immutable.
Les implémentations immuables sont trop restrictives pour être créées sans le savoir, mais vous pouvez obtenir les avantages qu'elles méritent. De plus, le sentiment d'accomplissement lorsqu'il est mis en œuvre avec succès est un plaisir réservé aux programmeurs (rires). Même dans la révision du code, vous devriez pouvoir être considéré par vos aînés comme "ce type, ne le faites pas". Le plus gros problème, la copie défensive, peut être atténué en combinant les implémentations Immutable, ce qui est également une raison pour éliminer Mutable.
L'implémentation sans état semble être plus sujette aux bogues (donc appelée anti-pattern) avec de nombreuses précautions, mais si elle est gérée correctement, elle a le potentiel d'être une implémentation hautement réutilisable et ne se fanant pas. Je suis.
Il est également important de noter que l'implémentation de méthodes ayant des effets secondaires, même des objets immuables, peut affecter d'autres objets mutables.
Si vous avez lu jusqu'ici, je pense que vous pouvez déjà programmer consciemment en apprenant les mots Mutable (variable) et Immutable (immuable). Si vous révisez le code que vous avez écrit jusqu'à présent, n'est-ce pas mieux maintenant?
Enfin, si vous voulez en savoir plus sur l'immuabilité et d'autres bonnes méthodes d'implémentation, qui sont uniquement Java, je vous recommande le livre "Effective Java".
Recommended Posts