Almost everytime I discuss OOP and code design with someone, right after I tell them that I do not like the concept of get/set “model objects”, I see wide eyes accompanied by the question: “But how do you work without a model?”
A fair question. We must have a “model” in our code, we must represent objects and their data somehow, right? If you read my previous articles the answer should be more or less obvious: the object’s interface is the model!
I already wrote about how I animate standard DTOs rather than parsing them and shoving the data into a dead POJO. Now let’s see a more elaborated example.
Check the following class:
It looks strange, doesn’t it? We don’t have the prices of the Cars that our app is working with, neither do we want to hardcode them somewhere. So, there is class MarketPrice which fetches a Car’s price from a 3rd-party webservice… the Price fetches itself.
It doesn’t look natural because we were taught that the objects in our code must respect the real-life entities they represent. Since the price is a lifeless, abstract concept, we were taught to represent it as a get/set bag of data – that HTTP call is not its job, it’s the job of a “controller”. As you know already, I think that this way of thinking leads to huge, unmaintainable software, where refactoring becomes impossible and code quality drops significantly with each new functionality.
MarketPrice class would be used as follows:
You see? The “business logic”, which uses this Car does not care where the Price comes from, it only works with the model, the interface. However, we automatically got rid of a static method somewhere, which would make the HTTP call and parse the output; that logic is encapsulated and easily tested with class MarketPrice.
But wait, we have at least 2 issues with this design:
- Efficiency; MarketPrice.value() will make an HTTP call everytime it is called.
- Consistency; The price may change, so everytime you call MarketPrice.value(), you may get a different number.
Efficiency, if needed, can be easily fixed with a “caching decorator”:
Done; any implementation of Price, if wrapped inside this decorator, will fetch the value exactly once and cache it. This also fixes the consistency problem, we can be sure that a filter, once applied, will remain valid (the values won’t change at the next call).
Still, in our scenario, maybe we shouldn’t cache the prices. Again, our app doesn’t hold any information about them, so it makes sense to always have them updated, to respect the market. How do we filter the cars then? Without a caching mechanism, the following code may introduce bugs:
You see, after the creation of the
cheaper cars List, the price of a Car may change and become > 10.000. Yet it is still in the list. How do we fix this?
An elegant solution, I believe, is to let an object of type Cars handle more instances of Car. Yet another strange thing. Why can’t we simply use a List? Because lists are also dumb containers of data, as you see above. They know nothing and we cannot always rely on them to hold valid values. Instead, if we implement our own plural, we can tune up the Iterator and make the filtering dynamic (interface Cars extends Iterable):
The cool iterator is the one returned by
FilteredCars.iterator(). It runs over the encapsulated
Cars and makes sure it jumps over (or even removes) the cars which do not respect the given Price.
Now we have the guarantee that, at iteration time, a Car respects our filter.
What do you think? Is it harder to design? Definetly, the architect has a tough job. But once the design is in place, implementation & testing are easy and decoration is a piece of cake – each of the above classes are easily tested and extended through decoration (which would not be possible without interfaces). Besides, we have no stale, untested code, no static methods and no spaghetti code anywhere.
To summarize, keep in mind the following: traditional get/set models are mere syntactic sugar. They are dead and cannot do anything for us. There is no difference between
xml.getElement("price") – any logic related to the price is still scattered around somewhere. Why shouldn’t this logic be inside a live, smart object, which has an interface?