Problem overview
A thermostat keeps a target temperature, and the target must always stay between 10 and 30 degrees. If any code could change the target directly, nothing would stop it from setting 95. So the target is a private field: only the thermostat's own methods can change it, and each of them keeps it in range.
Thermostat()starts with a target of 20.setTarget(t)sets the target tot, but a value below 10 gives 10 and a value above 30 gives 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. Two calls to warmer() give 22, and cooler() brings it to 21.
Approach: one place that enforces the rule
The range rule belongs in one method, setTarget. If warmer and cooler simply call setTarget with target + 1 or target - 1, they get the rule for free: at 30, warmer() asks for 31 and setTarget turns it back into 30.
Step by step
- Declare the field at the top of the class:
private var target: Int = 20. It is avarbecause the methods change it, andprivateso that only they can. - In
setTarget, handle a low value. Ift < 10, settargetto 10. A value that is too low isn't refused; it becomes the lowest target allowed. - Handle a high value with
else if (t > 30), settingtargetto 30. - Every other value goes in the
elsebranch and is stored as it is. Because 10 and 30 themselves are allowed, they end up here. warmercallssetTarget(target + 1).coolercallssetTarget(target - 1).getTargetreturnstarget.
The solution in Scala
class Thermostat {
private var target: Int = 20
def setTarget(t: Int): Unit = {
if (t < 10) {
target = 10
} else if (t > 30) {
target = 30
} else {
target = t
}
}
def warmer(): Unit = {
setTarget(target + 1)
}
def cooler(): Unit = {
setTarget(target - 1)
}
def getTarget(): Int = {
return target
}
}
Scala details:
privateis checked by the compiler. Code outside the class that writest.target = 95is rejected: "variable target cannot be accessed as a member of (t : Thermostat) ... private variable target can only be accessed from class Thermostat."- Methods can call each other by their plain names.
warmercallssetTarget(...)on the same object. if/else if/elseworks as a chain: the first condition that is true decides, and the rest are skipped.
Using the class
val th = new Thermostat
println(th.getTarget())
th.warmer(); th.warmer()
println(th.getTarget())
th.cooler()
println(th.getTarget())
th.setTarget(35)
println(th.getTarget())
th.warmer()
println(th.getTarget())
th.cooler()
println(th.getTarget())
th.setTarget(4)
println(th.getTarget())
th.cooler()
println(th.getTarget())
th.setTarget(10)
th.warmer()
println(th.getTarget())
It prints:
20
22
21
30
30
29
10
10
11
35 becomes 30, and warmer() leaves it at 30. After cooler() it is 29. 4 becomes 10, cooler() leaves it at 10, and after setTarget(10) a warmer() gives 11.
Why a private field
With a public var, any line of code in the program could write t.target = 95, and the class could not stop it. Making the field private turns the rule into a promise the compiler helps keep: the only code that can touch target is inside the class, and all of it goes through the same check. This is called encapsulation. The class decides what is allowed, and the rest of the program uses the public methods.
It also explains getTarget. Other code still needs to know the temperature, but reading it through a method gives nobody a way to change it. If the rule changes later, say the range becomes 5 to 35, only the numbers inside setTarget change, and everything that uses the thermostat keeps working.
What happens at the edges
The calls at the limits are the ones worth tracing. When the target is 30, warmer() calls setTarget(31). The first test, 31 < 10, is false, the second, 31 > 30, is true, so the target is set to 30 and does not change. Likewise at 10, cooler() calls setTarget(9), which lands in the first branch and sets 10.
The order of the branches does not matter for correctness here, because no number is both below 10 and above 30. What matters is that every number falls into exactly one branch, so the target is always set to something, and that something is always in range.
The program above printed this behaviour: after setTarget(35) the target was 30, a warmer() kept it at 30, and a cooler() took it to 29.
Complexity
Every method does a few comparisons and at most one assignment, so each runs in O(1) time with one stored integer.
Testing
Use a new thermostat for each sequence:
getTarget(),warmer(),warmer(),getTarget(),cooler(),getTarget()give 20, 22 and 21.setTarget(35),getTarget(),warmer(),getTarget(),cooler(),getTarget()give 30, 30 and 29.setTarget(4),getTarget(),cooler(),getTarget(),setTarget(10),warmer(),getTarget()give 10, 10 and 11.
The second one checks the top limit, and the third checks the bottom limit and that 10 itself is accepted. Values such as -1000 and 1000 follow the same two branches and end at 10 and 30.
Common mistakes
- Changing the field directly in
warmer.target += 1skips the range check, and the target can reach 31. - Using
<=or>=. 10 and 30 are valid targets and must be kept as they are. - Refusing out-of-range values instead of moving them to the nearest limit.
- Leaving the field public. Without
private, other code could set any value.
