This is an AMP version of the article, its original content can be found here.
Object Behavior Must Not Be Configurable
Using object properties as configuration parameters is a very common mistake we keep making mostly because our objects are mutable—we configure them. We change their behavior by injecting parameters or even entire settings/configuration objects into them. Do I have to say that it's abusive and disrespectful from a philosophical point of view? I can, but let's take a look at it from a practical perspective.
Let's say there is a class that is supposed to read a web page and return its content:
Looks simple and straight-forward, right? Yes, it's a rather cohesive and solid class. Here is how we use it to read the content of Google front page:
Everything is fine until we start making this class more powerful.
Let's say we want to configure the encoding. We don't always want to use
We want it to be configurable. Here is what we do:
Done, the encoding is encapsulated and configurable. Now, let's say we
want to change the behavior of the class for the situation of an empty
page. If an empty page is loaded, we want to return
"<html/>". But not
always. We want this to be configurable. Here is what we do:
The class is getting bigger, huh? It's great, we're good programmers and our code must be complex, right? The more complex it is, the better programmers we are! I'm being sarcastic. Definitely not! But let's move on. Now we want our class to proceed anyway, even if the encoding is not supported on the current platform:
The class is growing and becoming more and more powerful! Now it's time
to introduce a new class, which we will call
PageSettings is basically a holder of parameters, without any
behavior. It has getters, which give us access to the parameters:
getEncoding(). If we keep
going in this direction, there could be a few dozen configuration settings
in that class. This may look very convenient and
is a very typical pattern in Java world. For example,
This is how we will call our highly configurable
PageSettings is immutable):
However, no matter how convenient it may look at first glance, this approach is very wrong. Mostly because it encourages us to make big and non-cohesive objects. They grow in size and become less testable, less maintainable and less readable.
To prevent that from happening, I would suggest a simple rule here: object behavior should not be configurable. Or, more technically, encapsulated properties must not be used to change the behavior of an object.
Object properties are there only to coordinate the location
of a real-world entity, which the object is representing. The
uri is the
coordinate, while the
alwaysHtml boolean property is a behavior changing
trigger. See the difference?
So, what should we do instead? What is the right design? We must use composable decorators. Here is how:
Here is how our
DefaultPage would look (yes, I had to change
its design a bit):
As you see, I'm making it implement interface
TextPage decorator, which converts an array of bytes to a text using
And finally the
You may say that
AlwaysTextPage will make two calls to the encapsulated
origin, in case of an unsupported encoding, which will lead to a duplicated
HTTP request. That's true and this is by design. We don't want this
duplicated HTTP roundtrip to happen. Let's introduce one more class,
which will cache the page fetched (
not thread-safe, but it's not important now):
Now, our code should look like this (pay attention, I'm now using
This is probably the most code-intensive post on this site so far, but I hope it's readable and I managed to convey the idea. Now we have five classes, each of which is rather small, easy to read and easy to reuse.
Just follow the rule: never make classes configurable!