This is a mobile version, full one is here.
Yegor Bugayenko
27 October 2020
New Metric: the Distance of Coupling
Encapsulation,
as you know, is one of the
four key principles
in object-oriented programming.
Encapsulation, according to Grady Booch et al.,
is “the process of hiding all the secrets of an object
that do not contribute to its essential characteristics.”
Practically speaking, it’s about those private
attributes that we use in Java and C++: they are not visible to the users of
our objects, that’s why they can’t be modified or even read.
Booch et al. believe that the purpose of encapsulation is
“to provide explicit barriers among different abstractions,”
which leads to “a clear separation of concerns.”
However, does it really work as planned? Do we really have
explicit barriers between objects? Let’s see.
First, I’m not the first and not the only one asking this question.
David West much earlier said that “in most ways,
encapsulation is a discipline more than a real barrier,” and
that “seldom is the integrity of an object protected in any absolute sense”.
In practice, “it is up to the user of an object to respect that object’s encapsulation.’’
Indeed, let’s take a look at the class Temperature
from my blog post
about naked data:
class Temperature {
private int t;
public int getT() { return this.t; }
public void setT(int t) { this.t = t; }
}
Can we say that the attribute t
is truly encapsulated?
Technically, it is: it’s impossible
to modify it directly via the dot notation.
Simply put, we can’t do this:
Temperature x = new Temperature();
x.t = 10;
And we can’t even do this:
int y = x.t;
However, we can do exactly the same via the
getter getT()
and the setter setT()
.
Thus, the designer of the class Temperature
gives us the ability to access
its attribute, but indirectly, through
getters and setters.
I would say
that the principle of encapsulation is being violated here, and, I’m sure,
Allen Holub
would agree with me. What is the solution? The article
about naked data
proposed the use of the TellDontAsk principle
and that we should get rid of the getter:
class Temperature {
private int t;
public String toString() {
return String.format("%d F", this.t);
}
}
Now the class Temperature
doesn’t allow us to read its attribute t
.
Instead, we can only tell it to prepare a string presentation of the temperature
and return that back to us. Maybe not exactly a classic example of the “tell” paradigm,
since some data is coming back, but now it looks much better than before. The beauty
of this refactoring is less coupling
between the client and the object. With
the getter (or direct access to the attribute via dot notation), the client
was able to retrieve the numeric value of the temperature and recalculate it
in Fahrenheit, assuming that it was in Celsius. With the String
being
returned the client would not do this. The string would only be used as a final
product, not modifiable. Or maybe not?
What if the client does this:
Temperature x = new Temperature();
String txt = x.toString();
String[] parts = txt.split(" ");
int t = Integer.parseInt(parts[0]);
How does it look now? Isn’t this a violation of encapsulation?
The result of toString()
is not treated
as it is supposed to be treated. Not as a solid string, but as data with
some internal structure, which is known to the client. The knowledge the
client possesses about the output is the key problem here. The client knows too
much and uses this knowledge for its own benefit: to deconstruct the
data and manipulate the result.
Can we really prohibit the client from doing this? There is no such feature
in any programming language, to my knowledge. When the output of the method
is delivered to the client, the client is allowed to do whatever is needed
with it. This is that lack of respect to encapsulation, if I correctly
understood Dr. West. And we are not even discussing
the Reflection API, which
would allow us to take the t
out of Temperature
without even calling any methods.
Thus, encapsulation is not an explicit barrier. It exists for as long as we have
the desire to respect it. If we don’t, nothing can stop us from abusing an
object in any way we want. And even private
attribute modifiers won’t help.
Moreover, they will only create an illusion of encapsulation, while in reality
everyone is able to do whatever they feel is suitable for their business case.
I have a proposal, though, which may help us make encapsulation more explicit.
What if we had the ability to control what’s happening with the data
and objects after we return them to clients? What if we could prevent the client
from doing a split()
on the output of the toString()
method? We could do this at compile time, I think,
by going through all the code and checking how far the moments of interaction with our
objects are from the places where they were returned. In the example above,
the distance is two: 1) first, we do the split
, 2) second, we do the parseInt()
.
Larger applications will have bigger numbers, of course.
It seems that we can use this distance number as a metric for coupling between objects in the entire app. The larger the number (or the mean of all numbers), the worse the design: in good design we are not supposed to take something out of a method and then do some complex processing. We are, according to the TellDontAsk principle mentioned above, supposed to let our objects do the work and only return a quick summary of it. The distance metric will tell us exactly that: how many times, and by how much, we violated the principle of loose coupling.
Would you be interested in creating such an analyzer for, say, Java code?