Just-in-time designVery often programmers, who read relevant publications on patterns and principles (such as Martin Fowler’s or Gang of Four) start using them in practice to increase the quality of their code. This is of course a very good sign – it means that they care. The potential issue that appears in some of these cases is that they use them too often and protect themselves against any potential danger that might come in the future. This introduces a higher complexity of their code and usually results in lower readability and more time required to introduce new pieces of the code. Solution to make your code just as complex as needed at a certain moment, but still adequately flexible is to use an approach of just-in-time design. There is no formal definition of this term, however shortly speaking, it means that you provide a certain design element when it is really needed. Ha! I’m sure some of you will completely disagree with that. And of course those who do are partially right – sometimes we think we already know that something will be needed sometime in the future, so why not provide an appropriate design for this now? Well the truth is that we live in a very rapidly changing world and the same applies to software. We cannot predict the future, so we should focus on well-know and current requirements instead of thinking of the ones that probably will emerge in the long-term future. To summarize: just-in-time design means that we emerge our design based on what we know and when we know it, not based on rough prediction. Enough of theory, let’s move to some examples to make you more aware of how to apply this in some real-world scenarios.
Open-Closed PrincipleOne of the most well-known software principles is the one called Open-Closed:
“Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.”The above means that these entities should allow their behavior to be modified, but without altering their code. Complying with these rules considerably minimizes changes to existing code and the risk of breaking it when adding new functionalities. There are many ways of complying with this principle, the most obvious ones are through inheritance or polymorphism. Let’s say that we have initially class A with two public methods: Method1 and Method2. Then, somewhere in the future we suddenly need to change some aspects of the behavior of Method2. We could do this directly in class A (by e.g. adding some “if” statement in Method2 of this class) which will break the rule of the class being “closed for modifications”. A better solution would be to create a class B that will inherit from class A and make a change in the overridden version of Method2 (see the picture below). The class B will now contain only what is different and new and will, thanks to this, be simpler and safer to work with. This example is called “Open-Closed through direct inheritance” and is meant to increase the reusability by means of inheritance. There is also another solution to this challenge. Let’s come back to the initial design and assume that the Method2 of class A uses Method2_Service_Version1 which inherits from abstract class Method2_Service to accomplish its behavior:
}Now, when changing the behavior of Method2 we can add a new class Method2_Service_Version2 where a new behavior will be placed. This will make no changes to the existing code in class A and in Method2_Service_Version1 and therefore we have complied with Open-Closed through polymorphism. So: should we apply Open-Closed wherever we see a chance to increase reusability and flexibility? Well, not really – we should not forget that applying such patterns as O/C also increases the complexity of our code. Then, when should we apply it? The answer is simple: just in time.
Refactor to Open-ClosedLet’s consider the following example: We want to create a very simple game. Let’s call it World of Tanks (assuming it is not existing yet on the market). Firstly, we need to validate it with the end users to check whether there is any potential for such game to become popular. As the first step we need to design the Tank. We assume that in the first version of the game there will be just one type of tanks, with one weapon, etc. We could do this in the following way:
But then you could say it is not complying with Open-Closed and is not flexible. Okay, let’s make it Open-Closed through polymorphism:
We can say that O/C has been appropriately applied and we have improved the flexibility of our code – now we just need to wait for the changes to take advantage of our work. The only problem is that none of the possible variations taken into account when designing is varying at the moment, whereas we have already a design that is far more complex than we indeed need. Accommodating the current design to all possible variations will usually tend to produce designs that are overly complex. Of course you can say that there are many things that are not varying at the moment but are likely to vary sometime in the future. The problem is that it is just a prediction and you never know which of your predictions will come true.
To face the above challenges a term called “refactor to Open-Closed” has been introduced. Refactoring to the open-closed allows us to introduce design elements as they are needed but not before. Thanks to this we can proceed with “just-in-time” based on what we know and when we know it instead of on pure prediction.Let’s say we have coded our solution according to the initial design which is not complying with Open-Closed. Sometime in the future a new requirement arises: “We need to provide our tanks with a new type of a weapon“. We need to accommodate the Fire method to adjust it to this requirement. We could put these changes directly inside this method, but this is not the way to go. It’s definitely better to extract a class as shown on the picture below. Then we should extract interface: And finally add a new class that will implement the extracted interface and will cover the new requirement:
The behavior of Tank has not been changed. We have performed a just-in-time refactor with the compliance to Open-Closed principle.