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 "UTF-8". 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:
Class PageSettings is basically a holder of parameters, without any behavior. It has getters, which give us access to the parameters: isEncodeAnyway(), isAlwaysHtml(), and 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, look at JobConf from Hadoop. This is how we will call our highly configurable Page (I'm assuming 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?
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 Page. Now TextPage decorator, which converts an array of bytes to a text using provided encoding:
Now the NeverEmptyPage:
And finally the AlwaysTextPage:
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 OncePage):
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!