Problem overview
A thermostat keeps a target temperature, and the target must always stay between 10 and 30 degrees. If any code could assign to the target directly, nothing would stop it from setting 95. So the class makes the target private: only the thermostat's own methods can change it, and every one of them keeps it in range.
Thermostat()starts with a target of 20.setTarget(t)sets the target tot, but a value below 10 becomes 10 and a value above 30 becomes 30.warmer()raises the target by 1 andcooler()lowers it by 1, never leaving the range 10 to 30.getTarget()returns the target. It is the only way for other code to read it.
A new thermostat reports 20. After two calls to warmer() it reports 22, and after a cooler() it reports 21. setTarget(35) gives 30, and a following warmer() leaves it at 30.
Approach: keep the rule in one place
There are three ways the target can change (setTarget, warmer, cooler), and each must respect the same limits. Writing the range check three times invites a bug in one of them. Instead, only setTarget contains the check. warmer calls setTarget(target + 1) and cooler calls setTarget(target - 1), so at 30 the value 31 is turned back into 30, and at 10 the value 9 is turned back into 10.
The private keyword makes this reliable. Code outside the class can't bypass the checks, so the rule holds for every thermostat object whatever the rest of the program does. Making a field private and offering methods to read and change it is called encapsulation.
Step by step
- Declare a
private int target. Only code insideThermostatcan use it. - The constructor sets
targetto 20. - In
setTarget, handle a value that is too low: ift < 10, set the target to 10. The value is not refused; the target becomes the lowest one allowed. - Handle a value that is too high with
else if (t > 30), setting the target to 30. - Otherwise use
tas it is. The values 10 and 30 themselves are allowed, so they end up in this branch. warmercallssetTarget(target + 1).coolercallssetTarget(target - 1).getTargetreturnstarget.
The solution in Java
class Thermostat {
private int target;
public Thermostat() {
target = 20;
}
public void setTarget(int t) {
if (t < 10) {
target = 10;
} else if (t > 30) {
target = 30;
} else {
target = t;
}
}
public void warmer() {
setTarget(target + 1);
}
public void cooler() {
setTarget(target - 1);
}
public int getTarget() {
return target;
}
}
Notes on the Java:
- The constructor has no parameters, so there is nothing to confuse with the field and
this.isn't needed. - One method can call another of the same object by name:
warmercallssetTargetdirectly. - A getter such as
getTargetgives read access without write access. - Outside the class,
thermostat.targetdoesn't compile. The compiler reportserror: target has private access in Thermostat.
The three branches cover every integer exactly once: everything below 10, everything above 30, and everything else. Because the branches are chained with else, only one of them runs per call.
Using the class
Thermostat th = new Thermostat();
System.out.println(th.getTarget());
th.warmer();
th.warmer();
System.out.println(th.getTarget());
th.cooler();
System.out.println(th.getTarget());
th.setTarget(35);
System.out.println(th.getTarget());
th.warmer();
System.out.println(th.getTarget());
th.setTarget(4);
System.out.println(th.getTarget());
th.cooler();
System.out.println(th.getTarget());
th.setTarget(10);
th.warmer();
System.out.println(th.getTarget());
prints:
20
22
21
30
30
10
10
11
How setTarget treats each value
On a new thermostat, calling setTarget(v) and then getTarget() gives these results:
| v | target afterwards | Why |
|---|---|---|
| -1000 | 10 | below 10, so the lowest allowed |
| 9 | 10 | just below the range |
| 10 | 10 | the lowest value is allowed as it is |
| 11 | 11 | inside the range |
| 29 | 29 | inside the range |
| 30 | 30 | the highest value is allowed as it is |
| 31 | 30 | just above the range |
| 1000 | 30 | above 30, so the highest allowed |
The same rule covers repeated steps. On a thermostat that starts at 20, calling warmer() 15 times ends at 30, not 35, and then calling cooler() 40 times ends at 10, not -10. Each call stays inside the range, so the limits hold however many calls come.
Why not check the range in the constructor only?
The constructor runs once, when the object is created, and it sets a safe value, 20. The danger comes later, every time the target changes. That is why the check lives in setTarget and why every other change goes through it. A rule that is checked in only one of several places is a rule that will eventually be broken.
Edge cases and common mistakes
- Refusing instead of clamping.
setTarget(35)doesn't leave the old target alone; it sets 30. LikewisesetTarget(4)sets 10. - Changing
targetdirectly inwarmer.target++would run past 30. Going throughsetTargetreuses the check. - Making the field
public. The tests would still pass, but any code could then settarget = 95, which is exactly what the class is meant to prevent. - A getter that changes state.
getTargetonly returns the value.
What private protects, and what it doesn't
private is checked by the compiler, not at run time. A line like th.target = 95 in another class never gets to run, because the program doesn't compile. Inside Thermostat, though, every method can still change target, so the class itself has to follow its own rule: that is why each method either goes through setTarget or never touches the field.
Each thermostat object has its own target. Raising one thermostat with warmer() leaves any other thermostat at its own value.
Complexity
Each method does a few comparisons and one assignment, so every operation is O(1) in time, and the object stores a single integer.
