Favor Immutable Over Mutable State

 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.29.22