An immutable instance variable keeps its initialized value unchanged. Remove its mutator: unnecessary setters permit invalid state and increase maintenance costs. section 8.3
Apply the pattern while maintaining the class invariant:
- Make as many instance variables immutable as practical. section 8.3
- Eliminate mutators for immutable variables instead of exposing trivial assignment methods. section 8.3
- Validate constructor inputs and mutator inputs through one shared validation method when mutation remains necessary. section 8.3
Circle radius and the trivial setter
A Circle with setRadius can accept -1, violating the intended radius rule. If radius must remain fixed after construction, remove setRadius; if it must change, validate every update and keep validation knowledge in one place. section 8.3
Listing 8.3 A class with a trivial nonvalidating mutator
class Circle {
private:
double radius;
public:
Circle() : radius(1) {}
Circle(double radius) : radius(radius) {}
double getRadius() { return radius; }
void setRadius(double radius) { this->radius = radius; } #1
};
int main() {
Circle c1(2);
std::cout << c1.getRadius() << '\n';
c1.setRadius(-1);
std::cout << c1.getRadius() << '\n';
}
Inspect the problematic Circle implementation, where a trivial mutator assigns an unchecked negative radius. section 8.3
Listing 8.5 A class with validation and a single source of knowledge
class Circle {
private:
double radius;
static double validateRadius(double radius) { #1
if (radius < 0)
throw std::invalid_argument("radius is negative");
return radius;
}
public:
Circle() : radius(1) {}
Circle(double radius) : radius(validateRadius(
radius)) {} #2
double getRadius() const { return radius; }
void setRadius(double r) { radius =
validateRadius(r); } #3
};
int main() {
Circle c1(2);
std::cout << c1.getRadius() << '\n';
Circle c2(-1); #4
std::cout << c2.getRadius() << '\n';
}
Compare the corrected implementation, which centralizes radius validation for construction and permitted mutation. section 8.3
