DEV Community

Renato Santos
Renato Santos

Posted on

[en-US] Polymorphic Data: an attempt to convince you to adopt a new design pattern

should you want to skip directly to the code, here is the Github repository

… after all, you never know what sticks these days! I previously said that I would try to sell this idea as a new design pattern. Maybe you'll buy it, maybe you'll laugh at me or maybe you'll appreciate my effort as a kind of consolation prize. It doesn't matter, I'm not actually trying to disseminate a new pattern (but then, again, you never know what sticks these days). My idea is to share a line of thought and have a little discussion - which might not work since it's likely very few people - if any - will read this.

Design Patterns such as strategy and chain of responsibility are great tools to solve problems in an object-oriented fashion, even more so when we adopt behavior composition. Behavior composition, in turn, is a tool that, in my point of view, is essential to build modern applications. It is so because it has deep impacts on both code design and application architecture: it enables an evolutionary architecture that undergoes constant and gradual change and enables applications to adapt to new needs and realities. Be that as it may, behavior composition is beyond the scope of this article and will be addressed in due course.

It's interesting how strategy and chain of responsibility complement themselves, after all, the execution chain itself could be seen as an execution strategy. In its most simple implementation, strategy is pretty straightforward: a set of different strategies that receive a given set of parameters to work on.

Deciding which strategy to use may not be that trivial, but that has nothing to do with the pattern itself. I recall a discussion where it was stated that strategy breaks the open-closed principle. The idea behind this statement is that whenever a new strategy is added, the code must be changed to address the new strategy creation, and thus the principle would be broken.

This statement is clearly wrong and is the result of a literal understanding of the principle. Should this be true, it would be impractical to reach the open-closed principle in any application whatsoever. It's highly unlikely that as software evolves, no existing code is changed since it's highly likely that new constructs will interact with old constructs and, as such, somewhere down the execution chain a change will have to be made in order to call the new code.

Open-closed is not about changing lines of code, it is about changing application and domain behavior. To add a new log message, a new object call, or a new condition to decide which strategy to use does not violate this principle - changing a buffering algorithm to compact data or changing the strategy decision mechanism from conditionals to reflection is.

Back to what matters - I'm sorry for the detour - strategy is a very useful pattern abstraction-wise. But, sometimes, its implementation isn't as simple as it may seem. There are cases in which different strategies may need different data and behavior to its execution. One might say that if your strategies need different inputs to their execution, there is a code design problem. I can't deny it might very well be a possibility, but for now, let's agree that this is not necessarily the case and this could be an honest problem.

The simplest solution might be to use a map, except that it's not a simple solution but rather a simplistic one. - it exposes data and removes behavior, a recipe for an anemic model that doesn't do well on changes. Still, I don't judge those who resort to such solution - let's just hope the winds of change are merciful or, better yet, that there are no winds of change.

A while back I had this problem and since I was not feeling comfortable using a map, I found myself in a tight spot. I started doing some research about this issue but without much success. I don't recall exactly what the research yielded, but I do recall a specific solution that was based on parameters abstraction. The solution details are out of scope here, but you can read further on Extending the Strategy Pattern for parametrized Algorithms

I won't discuss the applicability of the solution proposed above, it was certainly the result of research (and a lot of headaches). Suffice to say that it was not adequate to my case, just as any pattern can't be applied to every situation.

A brief explanation of my problem is in order. I was in a situation where it was needed to connect two distinct contexts (I'm talking about authentication and authorization here but this would be true in any context, really). The contexts were not known until runtime and any given context could be translated to any desired one - except for protocol incompatibilities. You might be asking what exactly do I mean by contexts - in this case are things like OAuth, SAML, Basic Authentication, etc.

The fruitless research motivated me to think of a possible way to solve this situation. After an ideation process, I arrived at the solution I call Polymorphic Data - more details about this process can be found in Modeling an OO domain: a case study about the creation of a model for an authentication and authorization gateway.

It wasn't that hard to come up with an idea on how to execute the transition itself - chain of responsibility was quite adequate. The issue was in providing data to each step.

IMHO, object orientation is about abstraction, about encapsulation. Polymorphic Data provides us a way to encapsulate data and behavior that, otherwise, would be available to all interacting parts, even though each part only needs a small set of it.

Polymorphic Data

The main idea behind the "pattern" is to enable a bigger data universe to be stored in a single repository while being made available on subsets of correlated data. Actually, it's not about making data available, but to make domain information and behavior available. This way, every strategy/step gets the same instance of PolymorphicData and requests its transformation to the adequate domain, having access solely to the needed behaviors and information.

The end result is that, in a situation where in order to reach a final objective, dynamic and diverse steps must be executed, a way is reached to share a repository of behaviors and data that specializes according to need, promoting encapsulation and high cohesion. In fact, PolymorphicData can be seen as a design that promotes a layer of anti-corruption.

Unlike the parameters solution linked above, I won't make an extensive article available. Here's the Github repository having the pattern's proof of concept. I recommend reading the README.MD and also the classes Javadoc.

That's it! :)

Top comments (0)