Problem overview
A thermostat keeps a target temperature, and the target must always stay between 10 and 30 degrees. The Thermostat class makes that true by keeping the target in a private field. Only the class's own methods can change it, and each one keeps it inside the range.
- A new thermostat has target 20.
SetTarget(t)sets the target, turning anything below 10 into 10 and anything above 30 into 30.Warmer()raises it by 1 andCooler()lowers it by 1, never leaving 10 to 30.GetTarget()returns it, and is the only way for other code to read it.
Starting at 20, two Warmer() calls give 22 and a Cooler() gives 21. After SetTarget(35) the target is 30, and Warmer() leaves it at 30.
Approach
If the field were public, any code could write thermostat.target = 95 and nothing would stop it. Making it private moves that control into the class. Code outside Thermostat that tries to use the field gets a compile error, so the rule only has to be written in one place.
That place is SetTarget. Warmer and Cooler do not repeat the range check: they call SetTarget with target + 1 or target - 1, and SetTarget turns 31 back into 30 or 9 back into 10. If the limits ever change, one method changes.
Step by step
- Declare
private int target. Only code inside the class can use it. - Constructor: set
targetto 20. - In
SetTarget, aniffort < 10sets the target to 10. - An
else iffort > 30sets it to 30. - An
elsesets it tot. The values 10 and 30 themselves are allowed, so they end up here unchanged. WarmercallsSetTarget(target + 1)andCoolercallsSetTarget(target - 1).GetTargetreturnstarget.
The solution in C#
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;
}
}
Using it:
var t = new Thermostat();
Console.WriteLine(t.GetTarget());
t.Warmer(); t.Warmer();
Console.WriteLine(t.GetTarget());
t.SetTarget(35);
Console.WriteLine(t.GetTarget());
t.Warmer();
Console.WriteLine(t.GetTarget());
t.SetTarget(4);
t.Cooler();
Console.WriteLine(t.GetTarget());
This prints:
20
22
30
30
10
What private does
Writing t.target in code outside the class does not compile. The compiler reports:
error CS0122: 'Thermostat.target' is inaccessible due to its protection level
This is a check made when the program is compiled, so a thermostat with a target of 95 can't be created by accident. Inside the class, every method can use target freely. GetTarget lets other code read the value without letting it change it. In C#, members without a modifier inside a class are private by default, but writing private says what you mean.
Edge cases and common mistakes
- Refusing instead of clamping. A value that is too high or too low is not ignored:
SetTarget(4)gives 10, andSetTarget(-1000)gives 10 as well. - Boundaries. 10 and 30 are allowed. Using
t <= 10would work by accident here, butt < 10says the rule as stated. - Repeating the check in
Warmer. It works but duplicates the rule. Going throughSetTargetkeeps it in one place. - At the edge. At 30,
Warmer()leaves 30. At 10,Cooler()leaves 10.
Testing
- New thermostat:
GetTarget()is 20. After twoWarmer(): 22. AfterCooler(): 21. SetTarget(35): 30.Warmer(): still 30.Cooler(): 29.SetTarget(4): 10.Cooler(): still 10.SetTarget(10), thenWarmer(): 11.
Another way to clamp
Forcing a value into a range is called clamping, and .NET has a method for it. Math.Clamp(t, 10, 30) returns 10 for anything below 10, 30 for anything above 30, and t otherwise. SetTarget could be a single line, and it behaves exactly like the if chain:
public void SetTarget(int t) {
target = Math.Clamp(t, 10, 30);
}
With it, SetTarget(-1000) gives 10, SetTarget(1000) gives 30, and SetTarget(10) followed by Warmer() gives 11. The lesson spells the branches out so you can see each case; in a file of your own, add using System; to use Math.
Each thermostat has its own target
The private field belongs to the object. If you create two thermostats and call Warmer() on the first, the first returns 21 from GetTarget() and the second still returns 20. Private does not mean shared: it means that only code inside the class can touch the field, whichever object it belongs to.
Why not a public field and trust the callers
With a public field, every piece of code that uses a thermostat would have to remember the 10 to 30 rule, and one forgotten check could put the object in an invalid state that other code then relies on. With a private field, the class can promise that every thermostat it creates is valid at all times. This is the main reason to hide a field: the class keeps its own promises, and its users can't break them.
Key takeaways
- A
privatefield can be used only inside its class. - Put the validity rule in one method and let the others call it.
- Getter methods let other code read a private value without changing it.
These properties hold even for the extreme inputs the statement allows, such as -1000 and 1000, because every path ends in SetTarget.
