Objects in object-oriented programming are There are several internal state management methods depending on the implementation method.
By correctly understanding this internal state management method Bugs are hard to occur, and even if they do occur, the cause can be easily identified. You will also be able to create programs that are easy to test.
In this article, I will explain about the invariance of the internal state. It also briefly touches on the side effects of the method.
--Object-oriented in general ... I would like to say that people who use the Java language --For up to 2nd grade programming --For those who don't understand Java at all, first of all, after you have written a class --It might be a little difficult if you don't know the instantiation
In this explanation, the code is created using "rock-paper-scissors" as an example. The following rock-paper-scissors hands (strategies) and referees are defined in advance. (Judgment of rock-paper-scissors has nothing to do with this subject, so I'm sorry for the rough implementation)
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;
}
}
The internal state of an object is the value (or a combination of multiple objects) of member variables (also called instance fields in Java) that each instantiated object has.
For example, in the following cases
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);
}
}
The object left
has an internal state (strategy
) of goo (ROCK
),
The object right
has an internal state (strategy
) of par (PAPER
).
It will be.
The content of the variable is not important for the subject "invariance" that we will discuss this time. It classifies when and how the content changes or does not change.
Invariant classifications "Mutable" and "Immutable", And let's talk about a little special "Stateless".
Mutable means "variable" An object whose internal state may change in the middle.
public class MutableHand {
private HandStrategy strategy;
public void setStrategy(HandStrategy strategy) {
this.strategy = strategy;
}
public HandStrategy getStrategy() {
return strategy;
}
}
The object has a state (= there is an instance field), and there is a method to change the state. This time there is a method that intentionally rewrites the state, Some methods can be rewritten unintentionally. More on this in the Immutable chapter.
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());
//The second time we change hands and play
left.setStrategy(HandStrategy.SCISSORS);
Referee.battle(left.getStrategy(), right.getStrategy());
}
The variable that stores the object is final
qualified,
Keep in mind that this does not make it immutable, but allows you to change the internal state of the object.
Do not create a new object when playing the second game, The state of the existing object is rewritten and executed.
If you implement a class without an invariant understanding, you will probably end up with this mutable implementation. This is because it is the easiest to implement and there are few things to consider. On the other hand, the Immutable implementation, which will be explained later, has some considerations and is quite troublesome.
In general, mutable implementations are difficult to write tests, and when bugs are introduced, they tend to be less reproducible and take longer to identify the cause. (Refer to the disadvantage column) So you shouldn't actively choose a mutable implementation. However, it is selected for negative reasons, mainly when it is difficult to make Immutable. (Why it can be difficult later)
-** Implementation amount is reduced ** If you just want to store the data, you can create it with a minimal implementation. (It is arguable whether it is the best)
-** You can change to another state without recreating the instance ** In terms of memory efficiency, it is efficient because it requires only changing fields and consumes less additional memory. It can also reduce the cost of instantiation, which can be good in performance-critical situations.
-** Method execution result differs depending on internal state ** Since the execution result of the method may depend on the internal state, it may not be possible to cover it unless you create as many test cases as there are combinations of states. It may not be possible to cover the test, especially in situations where the internal state changes from moment to moment.
-** Results change in the order of processing ** When testing multiple methods, if the internal state of the method executed immediately before for the test changes, it will affect the next test. Since it is expected that the processing will be more complicated during production, bugs are likely to be mixed if it is affected in order.
-** It is necessary to know when the status has changed ** If you try to reproduce when a bug occurs, you can not reproduce it unless you understand where and what kind of internal state the object caused it, or it is difficult to find "where the internal state changed" and investigate Often takes time.
-** Difficult to reuse already used objects ** If it has already been used (referenced), it is difficult to reuse it elsewhere. This is because there is a risk that a problem that is difficult to identify will occur if the internal state is unintentionally rewritten.
Immutable means "immutable" An object whose internal state does not change once instantiated.
public class ImmutableHand {
private final HandStrategy strategy;
public ImmutableHand(HandStrategy strategy) {
this.strategy = strategy;
}
public HandStrategy getStrategy() {
return strategy;
}
}
The object has a state, but there is no method to change the state. The state is determined by the constructor when instantiating. After that, the state must not be changed.
private static void execImmutable() {
final ImmutableHand left = new ImmutableHand(HandStrategy.ROCK);
final ImmutableHand right = new ImmutableHand(HandStrategy.PAPER);
Referee.battle(left.getStrategy(), right.getStrategy());
//The second time creates a new instance
final ImmutableHand left2 = new ImmutableHand(HandStrategy.SCISSORS);
Referee.battle(left2.getStrategy(), right.getStrategy());
}
After instantiation, the object cannot change its internal state, so If you want to change your hand a second time, you will need a new instance. However, if you don't have to change hands, it is guaranteed that it is safe to reuse the previously used object. If it is Immutable, the contents will not change.
Immutable implementations are generally considered to be superior to mutable implementations. Immutable implementations can solve some of the testability and bug-specific issues of mutable implementations. (See merit column)
You can pass parameters to determine the internal state when instantiating, but you cannot change the internal state at other times. This guarantees that no matter what you do with an already instantiated object, the internal state will not change, that is, the object will not be affected before or after it is reused.
Immutable implementations sound good, but You must adhere to very strict restrictions in order to enjoy its benefits.
One is that the internal state must be determined when instantiating as described above. The other is that the instance fields of Immutable objects must also be immutable.
Is the following implementation Immutable?
public class NonImmutable {
private final String[] word = {"Apple", "gorilla", "rap"};
public String[] getWord() {
return word;
}
}
At first glance, there is no method to change the internal state. However, when used as follows,
private static void execNonImmutable() {
NonImmutable non = new NonImmutable();
non.getWord()[1] = "Godzilla";
System.out.println(non.getWord()[1]);
}
Is the string displayed on the console Gorilla
or Godzilla
?
The correct answer is Godzilla
.
This implementation is not Immutable because if you leave a Mutable field out of the object as it is, it can be modified outside.
If you make an Immutable implementation, you have to be careful that the internal state of the instance field cannot be rewritten. (It is necessary that everything is invariant by going back to the internal state of the internal state ...)
The Java library has some features that make it immutable.
For example
Collections.unmodifiableList(list)
You can use the above method to create an immutable list from an existing list.
It's a good idea to use these convenience features to prevent accidental changes.
-** Results do not depend on the order of method execution ** Even if you test multiple methods in a test, they are not affected in order.
-** Easy to understand the status ** It is easy to understand the state because only the parameters at the time of instantiation affect the internal state.
-** Easy to reuse objects ** There is nothing to consider as it is guaranteed that the state of the object will not be affected even if it is reused.
-** Memory efficiency may be good ** Memory efficiency can be improved by creating the objects to be used in advance and implementing them so that they can be reused. However, this is the case when the pattern of states is limited and predictable. (If it is unpredictable, it will be a disadvantage.)
-** Memory efficiency may be poor ** You can't change the internal state once you instantiate it, so you basically have to create a new object with a different state. This works against many patterns of conditions.
-** Method execution result differs depending on internal state ** Since the execution result of the method may depend on the internal state, it may not be possible to cover it unless you create as many test cases as there are combinations of states. However, it is still easier to test than Mutable.
-** Restrictions complicate (or impossible) implementation ** Immutable implementation fields cannot be exposed to the outside world, which can force complex implementations such as defensive copies. Also, if the state cannot be specified at the time of instantiation, it is difficult to make it Immutable.
Stateless means "has no state" It means that the object has no state regardless of whether it is instantiated or not.
Stateless is also Immutable as a prerequisite. Invariant is classified as Immutable, but since it has some additional features, it is intentionally set as a separate frame.
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";
}
}
}
The object has no state. (No instance field)
The methods of each implementation class (Rock
, Paper
, Scissors
) return a fixed value. (= Results do not change)
Another Stateless object has already appeared.
It's Referee
.
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());
//The second time you get a new instance
final StatelessHand left2 = StatelessHand.of(HandStrategy.SCISSORS);
System.out.println(left2.getName() + " VS " + right.getName());
Referee.battle(left2.getStrategy(), right.getStrategy());
}
Stateless implementations almost always do not require instantiation.
Referee
is executing the battle
method without instantiating it.
The state of the object does not affect the result (return value).
At first glance, a stateless implementation doesn't seem to need to be instantiated,
Subclasses of StatelessHand
are instantiated.
This is a good way to achieve polymorphism even with stateless implementations.
Stateless object methods have the same result if they have the same arguments. However, if you are using non-argument (eg static) objects inside the method, it doesn't seem to give the same result. This is syntactically not an argument, but it acts as a "hidden argument" for executing the method. It's a different issue than the fact that the result doesn't depend on the state of the stateless object.
When implementing "strict Stateless [^ 1]" (only arguments affect the result), you should use immutable objects other than arguments.
Subclasses of StatelessHand
are strict Stateless because they use the immutable objects ʻEnum and
String` in their methods.
[^ 1]: "Strict Stateless" is a coined word that I coined without permission, so it cannot be understood by other people (sweat)
System.lineSeparator ()
(returning a newline code).-** Memory efficiency is almost the highest ** An instance can be the only one in your application, as you can reuse objects that have no state. Depending on the implementation, you may not even need to instantiate it.
-** Results do not depend on the order of method execution ** Even if you test multiple methods in a test, they are not affected in order. (There are notes. See the side effect column.)
-** The method always returns the same result if the arguments are the same ** It's often easy to create test cases, because the result of a method depends on the arguments in any state of the application. (However, you need to be careful about "hidden arguments")
-** Cannot be encapsulated ** Encapsulation (here, meaning to combine data and processing), which is a feature and advantage of object-oriented programming, cannot be done in a Stateless implementation because there are no instance fields.
-** There are few turns (looks like) ** There is almost no turn other than the utility class. However, what you define as a class means. In fact, Stateless objects are frequently used in Java 8 and later.
There was a time when stateless objects were rarely used in object-oriented programming a decade ago, or rather were described as anti-patterns. (Since there is no state, the method is defined as static without instantiation, etc.)
However, the introduction of functional programming know-how to object-oriented programming has given Stateless an important position. I won't cover all the know-how that has been brought to you here, but one example is ** Lambda **.
Lambda in Java actually uses objects with a Stateless implementation.
The following example is a method that passes a and b as arguments and a lambda expression that performs a calculation on those two values.
calc(a, b, (x,y)->x+y)
If you call this method without using lambda, it will look like this:
calc(a, b, new BiFunction<Integer, Integer, Integer>() {
@Override
public Integer apply(Integer x, Integer y) {
return x + y;
}
});
We instantiate and pass a class that implements an interface called BiFunction
.
Lambda is a description method that simplifies the implementation and instantiation of a class to the utmost limit, and the object is a Stateless implementation.
final int offset = /*Offset value*/;
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;
}
});
In the case of a lambda expression, you can clearly see that you are using an object other than the argument, so readability is good.
Lambda and method references that appeared in Java 8 (details are omitted) have different grammatical features from traditional Java and are difficult for beginners to understand, but With their advent, Stateless has come to find significant value, so it's a good idea to think about it without thinking it's useless or an anti-pattern.
There is a "side effect" in the expression that is often used (or feels like) mainly in functional programming.
A method of a Stateless object receives an argument and returns a result as a return value, but when passing this method, the internal state of the object passed as an argument changes, or the inside of the "hidden argument" explained earlier The change in condition is called a "side effect".
When this side effect occurs, it may behave as if the Stateless benefit "methods always return the same result" is not followed. Actually, it is executed with the internal state of the argument changed, so it will not be "if the argument is the same", but there is a risk of overlooking that and causing a bug that is difficult to reproduce.
Not all side effects are bad. (At least in object-oriented programming) Immutable implementations can rewrite the state of variable objects given as arguments, although their state does not change. (Methods that set up the internal state of the object passed as an argument are common techniques)
When implementing Immutable and Stateless, it is necessary to make the method without side effects or to specify the side effects in comments.
Mutable implementations are easy to create with few restrictions. If the advantages outweigh the disadvantages, you can include it in the options, but it will make you happy later if you first consider whether it can be implemented in Immutable.
Immutable implementations are so strict that you can't unknowingly make them, but you get the benefits they deserve. Also, the sense of accomplishment when implemented successfully is a pleasure given only to programmers (laughs). Even in code reviews, you should be able to be seen by seniors as "this guy, don't do it". The biggest problem, defensive copying, can be mitigated by combining Immutable implementations, which is also a reason to eliminate Mutable.
Stateless implementations have a lot of precautions and it seems that bugs are more likely to be mixed in (hence the name anti-pattern), but if handled properly, it has the potential to be a highly reusable and non-fading implementation. I have.
It's also important to note that implementing methods that have side effects, even immutable objects, can affect other mutable objects.
If you have read this far, I think that you are already conscious of programming by learning the words Mutable (variable) and Immutable (immutable). If you review the code you've written so far, aren't you able to write better now?
Finally, if you want to know more about immutability and other good implementations, which are Java-only, I recommend the book "Effective Java".
Recommended Posts