[JAVA] Beachten Sie den internen Zustand (Unveränderlichkeit) von Objekten und die Nebenwirkungen von Methoden

Objekte in der objektorientierten Programmierung sind Abhängig von der Implementierungsmethode gibt es mehrere interne Zustandsverwaltungsmethoden.

Durch korrektes Verständnis dieser internen Zustandsverwaltungsmethode Fehler sind schwer zu verursachen, und selbst wenn sie auftreten, kann die Ursache leicht identifiziert werden. Sie können auch Programme erstellen, die einfach zu testen sind.

In diesem Artikel werde ich die Unveränderlichkeit des inneren Zustands erläutern. Es wird auch kurz auf die Nebenwirkungen der Methode eingegangen.

Zielperson

――Ich möchte sagen, dass objektorientiert im Allgemeinen ... aber Leute, die die Java-Sprache verwenden

Vor der Erklärung

In dieser Erklärung wird der Code am Beispiel von "Janken" erstellt. Die folgenden Janken-Hände (Strategien) und Schiedsrichter werden im Voraus definiert. (Das Urteil von Janken hat nichts mit diesem Thema zu tun, daher tut mir die grobe Umsetzung leid.)

public enum HandStrategy {
	/**Schmiere*/
	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;
	}
}

Wie ist der interne Zustand?

Der interne Status eines Objekts ist der Wert (oder eine Kombination aus mehreren, falls vorhanden) Elementvariablen (in Java auch als Instanzfelder bezeichnet), über die jedes instanziierte Objekt verfügt.

Zum Beispiel in den folgenden Fällen

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);
    }
}

Der interne Zustand ("Strategie") des Objekts "links" ist goo ("ROCK"). Der interne Zustand ("Strategie") des Objekts "rechts" ist par ("PAPIER"). Es wird sein.

Der Inhalt der Variablen ist für das Thema "Unveränderlichkeit", das wir dieses Mal diskutieren werden, nicht wichtig. Es klassifiziert, wann und wie sich der Inhalt ändert oder nicht.

Invariante Klassifikationen "veränderlich" und "unveränderlich", Und ich werde ein kleines spezielles "Staatenloses" erklären.

Mutable-Mutable

Veränderlich bedeutet "variabel" Ein Objekt, dessen interner Zustand sich in der Mitte ändern kann.

Definition

public class MutableHand {

	private HandStrategy strategy;

	public void setStrategy(HandStrategy strategy) {
		this.strategy = strategy;
	}

	public HandStrategy getStrategy() {
		return strategy;
	}
}

Das Objekt hat einen Status (= es gibt ein Instanzfeld) und es gibt eine Methode zum Ändern des Status. Dieses Mal gibt es eine Methode, um den Zustand absichtlich umzuschreiben. Einige Methoden können unbeabsichtigt umgeschrieben werden. Mehr dazu im Kapitel Unveränderlich.

Lauf

	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());

		//Das zweite Mal wechseln wir den Besitzer und spielen
		left.setStrategy(HandStrategy.SCISSORS);
		Referee.battle(left.getStrategy(), right.getStrategy());
	}

Die Variable, in der das Objekt gespeichert ist, ist "final" -qualifiziert. Es ist wichtig zu beachten, dass sich dies nicht ändert und der interne Status des Objekts geändert werden kann.

Erstellen Sie beim Spielen des zweiten Spiels kein neues Objekt Der Status des vorhandenen Objekts wird neu geschrieben und ausgeführt.

Charakteristisch

Wenn Sie eine Klasse ohne unveränderliches Verständnis implementieren, erhalten Sie wahrscheinlich diese Mutable-Implementierung. Dies liegt daran, dass es am einfachsten zu implementieren ist und nur wenige Dinge zu beachten sind. Andererseits hat die unveränderliche Implementierung, die später erläutert wird, einige Überlegungen und ist ziemlich mühsam.

Im Allgemeinen sind veränderbare Implementierungen schwer zu schreibende Tests, und wenn Fehler eingeführt werden, sind sie in der Regel weniger reproduzierbar und dauern länger, um die Ursache zu identifizieren. (Siehe Nachteilsspalte) Sie sollten sich also nicht aggressiv für eine Mutable-Implementierung entscheiden. Es wird jedoch aus negativen Gründen ausgewählt, hauptsächlich wenn es schwierig ist, unveränderlich zu machen. (Warum es später schwierig sein kann)

verdienen

Fehler

Unveränderlich - unveränderlich

Unveränderlich bedeutet "unveränderlich" Ein Objekt, dessen interner Zustand sich nach der Instanziierung nicht ändert.

Definition

public class ImmutableHand {

	private final HandStrategy strategy;

	public ImmutableHand(HandStrategy strategy) {
		this.strategy = strategy;
	}

	public HandStrategy getStrategy() {
		return strategy;
	}

}

Das Objekt hat einen Status, es gibt jedoch keine Methode zum Ändern des Status. Der Zustand wird beim Instanziieren vom Konstruktor bestimmt. Danach darf der Zustand nicht mehr geändert werden.

Lauf

	private static void execImmutable() {
		final ImmutableHand left = new ImmutableHand(HandStrategy.ROCK);
		final ImmutableHand right = new ImmutableHand(HandStrategy.PAPER);

		Referee.battle(left.getStrategy(), right.getStrategy());

		//Erstellen Sie beim zweiten Mal eine neue Instanz
		final ImmutableHand left2 = new ImmutableHand(HandStrategy.SCISSORS);
		Referee.battle(left2.getStrategy(), right.getStrategy());
	}

Nach der Instanziierung kann das Objekt seinen internen Status nicht ändern Wenn Sie Ihre Hand ein zweites Mal wechseln möchten, benötigen Sie eine neue Instanziierung. Wenn Sie jedoch nicht den Besitzer wechseln müssen, ist garantiert, dass das zuvor verwendete Objekt sicher wiederverwendet werden kann. Wenn es unveränderlich ist, ändert sich der Inhalt nicht.

Charakteristisch

Unveränderliche Implementierungen werden im Allgemeinen als überlegen gegenüber veränderlichen Implementierungen angesehen. Die unveränderliche Implementierung kann einige der Testbarkeits- und fehlerspezifischen Probleme der veränderlichen Implementierung lösen. (Siehe Verdienstspalte)

Sie können Parameter übergeben, um den internen Status beim Instanziieren zu bestimmen, aber Sie können den internen Status zu anderen Zeiten nicht ändern. Dies garantiert, dass sich der interne Status unabhängig davon, was Sie mit dem bereits instanziierten Objekt tun, nicht ändert, dh das Objekt wird vor oder nach seiner Wiederverwendung nicht beeinflusst.

Die unveränderliche Implementierung klingt aber gut Sie müssen sehr strenge Einschränkungen einhalten, um die Vorteile nutzen zu können.

Zum einen muss der interne Zustand beim Instanziieren wie oben beschrieben bestimmt werden. Das andere ist, dass die Instanzfelder von unveränderlichen Objekten ebenfalls unveränderlich sein müssen.

Ist die folgende Implementierung unveränderlich?

public class NonImmutable {

	private final String[] word = {"Apfel", "Gorilla", "Rap"};

	public String[] getWord() {
		return word;
	}
}

Auf den ersten Blick gibt es keine Methode, um den internen Zustand zu ändern. Wenn jedoch wie folgt verwendet wird,

	private static void execNonImmutable() {
		NonImmutable non = new NonImmutable();
		non.getWord()[1] = "Godzilla";
		System.out.println(non.getWord()[1]);
	}

Wird die Zeichenfolge auf der Konsole "Gorilla" oder "Godzilla" angezeigt?

Die richtige Antwort lautet "Godzilla".

Diese Implementierung ist nicht unveränderlich, da ein veränderbares Feld außerhalb des Objekts unverändert geändert werden kann.

Es ist zu beachten, dass der interne Status des Instanzfelds bei einer unveränderlichen Implementierung nicht neu geschrieben werden kann. (Es ist notwendig, dass alles unveränderlich ist, indem man zum inneren Zustand des inneren Zustands zurückkehrt ...)

Die Java-Bibliothek verfügt über einige Funktionen, die sie unveränderlich machen. Zum Beispiel Collections.unmodifiableList(list) Mit der obigen Methode können Sie eine unveränderliche Liste aus einer vorhandenen Liste erstellen. Es ist eine gute Idee, diese Komfortfunktionen zu verwenden, um versehentliche Änderungen zu verhindern.

verdienen

Fehler

Staatenlos-Staatenlos

Staatenlos bedeutet "hat keinen Zustand" Dies bedeutet, dass das Objekt keinen Status hat, unabhängig davon, ob es instanziiert ist oder nicht.

Staatenlos ist auch unveränderlich als Voraussetzung. Unveränderlich wird als unveränderlich klassifiziert, aber da es einige zusätzliche Funktionen hat, wird es absichtlich als separater Rahmen festgelegt.

Definition

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 "Schmiere";
		}
	}

	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";
		}
	}
}

Das Objekt hat keinen Status. (Kein Instanzfeld) Die Methoden jeder Implementierungsklasse ("Rock", "Paper", "Scissors") geben einen festen Wert zurück. (= Das Ergebnis ändert sich nicht)

Ein weiteres zustandsloses Objekt ist bereits erschienen. Es ist "Schiedsrichter".

Lauf

	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());

		//Holen Sie sich beim zweiten Mal eine neue Instanz
		final StatelessHand left2 = StatelessHand.of(HandStrategy.SCISSORS);
		System.out.println(left2.getName() + " VS " + right.getName());
		Referee.battle(left2.getStrategy(), right.getStrategy());
	}

Zustandslose Implementierungen erfordern fast immer keine Instanziierung. "Schiedsrichter" führt die "Kampf" -Methode aus, ohne sie zu instanziieren. Der Status des Objekts hat keinen Einfluss auf das Ergebnis (Rückgabewert).

Auf den ersten Blick scheint die Implementierung von Stateless nicht instanziiert werden zu müssen. Unterklassen von "StatelessHand" werden instanziiert. Dies ist eine effektive Methode, wenn Sie auch mit einer zustandslosen Implementierung Polymorphismus erzielen möchten.

Charakteristisch

Zustandslose Objektmethoden haben das gleiche Ergebnis, wenn die Argumente gleich sind. Es scheint jedoch nicht dasselbe Ergebnis zu liefern, wenn Sie nicht argumentative (z. B. statische) Objekte innerhalb der Methode verwenden. Dies ist syntaktisch kein Argument, sondern fungiert als "verstecktes Argument" für die Ausführung der Methode. Es ist ein anderes Problem als die Tatsache, dass das Ergebnis nicht vom Status des zustandslosen Objekts abhängt.

Bei der Implementierung von "strict Stateless [^ 1]" (nur die Argumente wirken sich auf das Ergebnis aus) sollten Sie andere unveränderliche Objekte als die Argumente verwenden. Unterklassen von StatelessHand sind streng Stateless, da sie in ihren Methoden die unveränderlichen Objekte Enum und String verwenden.

[^ 1]: "Strict Stateless" ist ein Wort, das ich ohne Erlaubnis geprägt habe, damit es von anderen Menschen nicht verstanden werden kann (Schweiß)

verdienen

Fehler

Lambda

Es gab eine Zeit, in der zustandslose Objekte vor einem Jahrzehnt selten in der objektorientierten Programmierung verwendet wurden oder eher als Anti-Patterns bezeichnet wurden. (Da es keinen Status gibt, wird die Methode statisch definiert, ohne sie zu instanziieren usw.)

Die objektorientierte Einführung von funktionalem Programmier-Know-how hat Stateless jedoch eine wichtige Position eingeräumt. Ich werde nicht auf das gesamte hier eingebrachte Know-how eingehen, aber ein Beispiel ist ** Lambda **.

Lambda in Java verwendet tatsächlich Objekte mit einer zustandslosen Implementierung. Das folgende Beispiel ist eine Methode, die a und b als Argumente übergibt, und ein Lambda-Ausdruck, der eine Berechnung für diese beiden Werte durchführt. calc(a, b, (x,y)->x+y)

Dies passiert, wenn Sie diese Methode ohne Verwendung von Lambdas aufrufen.

calc(a, b, new BiFunction<Integer, Integer, Integer>() {
			@Override
			public Integer apply(Integer x, Integer y) {
				return x + y;
			}
		});

Wir instanziieren und übergeben eine Klasse, die eine Schnittstelle namens "BiFunction" implementiert. Lambda ist eine Beschreibungsmethode, die die Implementierung und Instanziierung einer Klasse bis zum Äußersten vereinfacht, und das Objekt ist eine zustandslose Implementierung.

final int offset = /*Offsetwert*/;
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;
			}
		});

Im Fall eines Lambda-Ausdrucks ist klar, dass ein anderes Objekt als das Argument verwendet wird, daher denke ich, dass die Lesbarkeit gut ist.

Lambda und Methodenreferenzen, die in Java 8 erschienen sind (Details werden weggelassen), haben andere grammatikalische Merkmale als herkömmliches Java, und es ist ein Mechanismus, der für Anfänger schwer zu verstehen ist. Mit ihrem Aufkommen hat Stateless einen bedeutenden Wert gefunden, daher ist es eine gute Idee, darüber nachzudenken, ohne zu denken, dass es keinen Nutzen hat oder ein Anti-Muster ist.

Nebenwirkungen

Es gibt einen "Nebeneffekt", der häufig in der funktionalen Programmierung verwendet wird (oder sich anfühlt).

Die zustandslose Objektmethode empfängt ein Argument und gibt das Ergebnis als Rückgabewert zurück. Wenn Sie diese Methode übergeben, ändert sich jedoch der interne Status des als Argument übergebenen Objekts oder das Innere des oben erläuterten "versteckten Arguments" Die Änderung des Zustands wird als "Nebenwirkung" bezeichnet.

Wenn dieser Nebeneffekt auftritt, kann es sein, dass der zustandslose Vorteil "Methoden geben immer das gleiche Ergebnis zurück" nicht befolgt wird. Tatsächlich wird es mit geändertem internen Status des Arguments ausgeführt, sodass es nicht "wenn das Argument dasselbe ist" ist, aber es besteht die Gefahr, dass dies übersehen wird und ein Fehler verursacht wird, der schwer zu reproduzieren ist.

Nicht alle Nebenwirkungen sind schlecht. (Zumindest in der objektorientierten Programmierung) Unveränderliche Implementierungen ändern ihren Status nicht, können jedoch den Status des im Argument angegebenen variablen Objekts neu schreiben. (Die Methode zum Einrichten des internen Status des als Argument übergebenen Objekts ist eine gängige Technik.)

Bei der Implementierung von Immutable and Stateless ist es erforderlich, die Methode frei von Nebenwirkungen zu machen oder die Nebenwirkungen in Kommentaren anzugeben.

Zusammenfassung

Veränderbare Implementierungen sind mit wenigen Einschränkungen einfach zu erstellen. Wenn die Vorteile die Nachteile überwiegen, können Sie es in die Optionen aufnehmen, aber es wird Sie später glücklich machen, wenn Sie zuerst überlegen, ob es in Immutable implementiert werden kann.

Unveränderliche Implementierungen sind zu restriktiv, um sie unwissentlich zu erstellen, aber Sie können die Vorteile nutzen, die sie verdienen. Auch das Erfolgserlebnis bei erfolgreicher Implementierung ist nur Programmierern ein Vergnügen (lacht). Selbst in der Codeüberprüfung sollten Sie von Ihren Senioren als "dieser Typ, tun Sie es nicht" angesehen werden können. Das größte Problem, das defensive Kopieren, kann durch die Kombination unveränderlicher Implementierungen gemildert werden. Dies ist auch ein Grund, Mutable zu eliminieren.

Die zustandslose Implementierung scheint mit vielen Vorsichtsmaßnahmen anfälliger für Fehler (daher als Anti-Pattern bezeichnet) zu sein. Bei ordnungsgemäßer Handhabung kann es sich jedoch um eine hochgradig wiederverwendbare und nicht verblassende Implementierung handeln. Ich bin.

Es ist auch wichtig zu beachten, dass die Implementierung von Methoden mit Nebenwirkungen, selbst unveränderlichen Objekten, andere veränderbare Objekte beeinflussen kann.

Wenn Sie so weit gelesen haben, denke ich, dass Sie bereits bewusst programmieren können, indem Sie die Wörter Veränderlich (variabel) und Unveränderlich (unveränderlich) lernen. Wenn Sie den Code überprüfen, den Sie bisher geschrieben haben, ist es jetzt nicht besser?

Wenn Sie mehr über Unveränderlichkeit und andere gute Implementierungsmethoden erfahren möchten, die nur Java verwenden, empfehle ich das Buch "Effective Java".

Recommended Posts

Beachten Sie den internen Zustand (Unveränderlichkeit) von Objekten und die Nebenwirkungen von Methoden
Fassen Sie den Lebenszyklus von Java-Objekten zusammen, die bei der Android-Entwicklung berücksichtigt werden müssen
Den aktuellen Status von Java organisieren und die Zukunft betrachten
Überwachen Sie den internen Status von Java-Programmen mit Kubernetes
Achten Sie auf die Speicherbereinigung und vermeiden Sie Speicherlecks
Ich habe versucht, die Methoden von Java String und StringBuilder zusammenzufassen