| class Distance { |
» | DistanceUnit unit; |
» | double value; |
| |
| Distance(DistanceUnit unit, double value) { |
| this.unit = unit; |
| this.value = value; |
| } |
| |
| static Distance km(double value) { |
| return new Distance(DistanceUnit.KILOMETERS, value); |
| } |
| |
| void add(Distance distance) { |
| distance.convertTo(unit); |
» | value += distance.value; |
| } |
| |
| void convertTo(DistanceUnit otherUnit) { |
| double conversionRate = unit.getConversionRate(otherUnit); |
» | unit = otherUnit; |
| value = conversionRate * value; |
| } |
| } |
By default, the state of objects is mutable. Whenever possible, you should make the state immutable because this makes objects harder to misuse.
The code here computes and converts distances for route planning. There’s no immediate bug in the code, but the problem is that the Distance class doesn’t defend itself from misuse. Consider the following code:
| Distance toMars = new Distance(DistanceUnit.KILOMETERS, 56_000_000); |
| Distance marsToVenus = new Distance(DistanceUnit.LIGHTYEARS, 0.000012656528); |
» | Distance toVenusViaMars = toMars; |
| toVenusViaMars.add(marsToVenus); |
We first create the distances from Earth to Mars in the variable toMars and from Mars to Venus in the variable marsToVenus. Then we compute the distance from Earth to Venus with a stopover at Mars in the variable toVenusViaMars.
The problem is that toVenus and toMars point to the same object. When we call toVenusViaMars.add(marsToVenus), we’re changing the value of toMars indirectly. If we reuse toMars later, we’re going to get a false distance value—especially if we pass the toMars instance around in a larger application where any code could alter it.
Let’s have the compiler make sure that this can’t happen.
| final class Distance { |
» | final DistanceUnit unit; |
» | final double value; |
| |
| |
| Distance(DistanceUnit unit, double value) { |
| this.unit = unit; |
| this.value = value; |
| } |
| |
| Distance add(Distance distance) { |
» | return new Distance(unit, value + distance.convertTo(unit).value); |
| } |
| |
| Distance convertTo(DistanceUnit otherUnit) { |
| double conversionRate = unit.getConversionRate(otherUnit); |
» | return new Distance(otherUnit, conversionRate * value); |
| } |
| } |
Objects should defend themselves from invalid changes, and you can achieve this by limiting their mutability.
Note the usage of the final keyword in the code above. Now you need to set the value and unit fields in the constructor, and you can’t change them afterward. If you compute a distance, you need a new instance every time:
| Distance toMars = new Distance(DistanceUnit.KILOMETERS, 56_000_000); |
| Distance marsToVenus = new Distance(DistanceUnit.LIGHTYEARS, 0.000012656528); |
| Distance toVenusViaMars = toMars.add(marsToVenus) |
| .convertTo(DistanceUnit.MILES); |
As you can see, we can no longer make the mistake we made before. The immutable state of a Distance prevents us from doing so. The downside of this solution is that we create more objects, but in Java small objects are cheap.
In software design, this solution is the way to go for so-called value objects[42]: percentages, money, currency, times, dates, coordinates and, of course, distances. These objects are indistinguishable if their values are equal: $10 is $10, even if there’s a different object representing each of those dollars. So watch out for value objects and make them immutable!
One more note on the final keyword before the class definition: this is required to ensure that this class can’t be extended anymore. Doing so would potentially allow us to add mutable state again.
18.191.29.22