Encapsulation – Why Getters, Setters, and Private Access Modifier Are Not Enough

encapsulation

In Clean Code:

public class Point {
   public double x;
   public double y;
}

The author wrote about the above Point() class:

This exposes implementation. Indeed, it would expose implementation
even if the variables were private and we were using single variable
getters and setters.

This means I must burn my school copybooks.

Why is combining getters, setters and private access modifier not enough?

Best Answer

The example given, of a Point class with two public members of type double named x and y, exposes its implementation by allowing anyone who has an object of type Point to know about its implementation. In this case, the entire implementation is exposed.

If you changed the public members to private and created a pair of getters and setters, you still expose its implementation. If you look at the interface of an object and see that it has methods like double getX(), double getY(), setX(double x), and setY(double y), then you aren't going to be sitting there thinking, "Gee, I wonder how they implemented that. I can't tell."

The second version of Point isn't really different from the first one, except that in most languages it will have a lot of unnecessary boilerplate. In both cases, looking at the object's interface tells you about how it is implemented.

If we wanted to change the internal representation of a Point to polar coordinates, we run into problems. We can still use the old interface, but it's clunky. A call to setX() is going to have to do an internal conversion to rectangular coordinates, change x, and then convert back to polar, introducing unnecessary rounding errors and extra computations.

If we had started out by designing an interface for a Point without reference to an implementation, we wouldn't have that sort of problem. Say we decide that a point has an x and y coordinate, and also an angle and magnitude. We could have double x(), double y(), double angle(), and double magnitude() methods. We could make them immutable, but let's say we didn't. So we need rectangular(double x, double y) and polar(double angle, double magnitude) to move the point.

What do we know about the implementation? Well, we know something, since a two-dimensional point is a very simple thing, but we don't know whether the object's internal representation is in polar or rectangular coordinates. The internal representation might even include both polar and rectangular coordinates, either with booleans to keep track of whether a particular set of coordinates is up to date, or else forcing everything to always be up to date automatically.

So in this case the implementation isn't exposed, since we can only guess how it was implemented.

Related Topic