I think a better example of encapsulation principles would be a normalized vector. There the design contract is that the vector's length must be exactly 1, and you would want to prevent someone from coming in and directly modifying the components to make it not be 1 anymore.
// this is only a 2-vec and you probably want a 3-vec, but for illustrative purposes...
struct Vector {
float x;
float y;
};
struct NormalizedVector
{
private:
float x_;
float y_;
public:
explicit NormalizedVector(const Vector& vec) {
const float length = sqrtf(vec.x * vec.x + vec.y * vec.y);
assert(length);
x_ = vec.x / length;
y_ = vec.y / length;
}
float x() const { return x_; }
float y() const { return y_; }
};
In this case, however, you wouldn't want to have individual setters for the components, because setting individual components would make it no longer normalized; it's not really a meaningful operation. You still might allow adding to or scaling a NormalizedVector, however, but then because of the contract (always length 1) you would return a regular vector (one with no restrictions enforced on it at the type level) from those methods.
Vector operator*(float scalar, const NormalizedVector& vec) {
return Vector { scalar * vec.x(), scalar * vec.y() };
}
Or perhaps a rotation operation…
NormalizedVector NormalizedVector::Rotate(float angle) {
// ...
}
In general, for objects that aren't simply data, that actually enforce a design contract, you want most mutators to be verbs, not simple setters. Even if your setters transform or validate their input somehow, calling them “SetFoo” is pretty weak encapsulation, since conceptually the method is still reaching inside the object to set a specific field on it, rather than carrying out some sort of meaningful operation that one can do with the type. If a class's mutators are mostly weak encapsulated “SetFoo” type methods, then this is where I'd start to question whether there's much in the way of a meaningful design contract here to begin with.