Polymorphic Input/Output Data
When developing any kind of code interface, whether it is an elegant object oriented package or one of those ugly “Service” classes that we are so accustomed to, it is obvious that it should be as extensible as possible and require as little maintenance as possible. This applies, of course, to the input and output parameters (returned types), especially if the code in question cannot be modified by its clients.
Question is, how can we have flexible parameters if they consist of dumb get/set models? Each time a new field/attribute is required, someone will knock on your door to update your interface because otherwise they won’t be able to send or receive all the data they need.
Take the bellow example:
Assume our Person
is a get/set class, having a firstName
, lastName
and an id
. When the user of this interface will need to receive or send more attributes they won’t be able to, so they’ll ask you to first alter the Person
model and then add logic in your code to handle those attributes.
The solution to this problem is Polymorphism, one of the most common OOP principles – it will make our parameters “transparent” and more maintainable. Assuming that our data is represented in JSON, here is how I would redesign the People
interface:
There are two big changes: Person
is actually an interface, extending javax.json.JsonObject
and at the same time animating some parts of the underlying json or maybe all of it – this way, if People.fetch(...)
changes and returns a bigger JsonObject
, the new values will be accessible automatically via JsonObject.getString(...)
method, until we may add more reading method to the Person
interface.
The second change is the input that insert(...)
method expects. It now requires a raw JsonObject
for the same reason: if the encapsulated service decides to expect more values, the user will simply add them to the JsonObject
. This may sound like an ugly solution since they might use it like this:
The above is quite ugly, the building of the JsonObject means duplicated logic. However, the user should realise that they can implement the interface PersonInput extends JsonObject
– all that building logic would be in the constructor of the PersonInput
implementation, while calls to people.insert(...)
will look like this:
Regarding the JsonObject
input parameter there is still one more question: how should the client know what values to provide? This is the part where JavaDocs come in the play: it should all be thoroughly documented in the method’s JavaDoc. I guess you can call it a good trade-off: the user has to do a little study before using your method but, on the other hand, the decoupling is very convenient for both of you.
The exact approach described above I used in the development of docker-java-api. Most methods which are creating something on the server are expecting JsonObject
as input and are returning interfaces which extend JsonObject
(see, for instance, Containers.create(…) and Container) – thus, if Docker’s API will evolve to require different Json inputs or return different Jsons, I won’t necessarily have to change any models any time soon.
P.S. I hope you noticed the keyword in this article is “interface”. It’s yet another example of why interfaces are very important tools that any object should make use of.