Introduction
Is software design hard? The answer to that question depends on what process is being followed when we initially start designing the software. Well good news is someone has already done hard work for us, come up with a set of principles. When followed we can mostly likely come up with maintainable, high cohesive and less coupled design. That someone is non-other than Robort C. Martin who came up with a set of principles for object orientation designs i.e. S.O.L.I.D in paper “Design Principles & Design Pattern” in 2000. In this discussion, I would like to touch on the ideas of these principles, starting with ‘Open Closed Principle‘ short hand OCP. Now let’s dive in discussion.
Open Closed Principle
Open closed Principle states that:
“System should be open for extension, but closed for modification”
What does this even mean, it looks so contradictory, how come something is open and closed at the same time? Well let me try to explain, it means that you should be able to add new features (i.e. extend) without modifying much of the well tested code(i.e. current code closed for modification). Why is this most important principle of all, because it tries to achieve loose coupling, maintainable & less buggy code.
If you think a bit of what we are really doing here is, not really touching any functional code which is well tested. Which implies less buggy code. It also promotes abstraction which makes the system loosely coupled, which implies easy to maintainable code.
Now you think this is nice, but this is very difficult to achieve in the real world, because of multiple reasons like constantly changing requirements, module dependencies etc. But if we could achieve partial OCP it has a huge impact on the overall system. Now you know what OCP is, now lets take a look at an example which is not following OCP followed by the one that follows OCP.
Design that is not following OCP
Let’s take an example of the logging system, where the logging class supports logging the data to File System, SQL Database. Now lets see the current Implementation of logging class. If you see the Log Api from the Logger class we are deciding where to log the data based on the type we receive from the client. So far things look okay.
package working.example.tech.SOLID.OCP;
public class Logger {
enum LoggerType {
WORKINGEXAMPLE_DB_LOGGER,
WORKINGEXAMPLE_FILESYSTEMLOGGER
}
public Logger() {
}
public void Log(Object obj, LoggerType loggerType, String data) {
if (loggerType == LoggerType.WORKINGEXAMPLE_DB_LOGGER) {
((DBLogger)obj).Log(data);
} else if(loggerType == LoggerType.WORKINGEXAMPLE_FILESYSTEMLOGGER) {
((FileSystemLogger)obj).Log(data);
}
}
}
public class FileSystemLogger {
public void Log(String data) {}
}
public class DBLogger {
public void Log(String data) {}
}
Now someone from the product team came up with a requirement after client feedback, where the client needs the capability to log the data in NoSql Database for their Analytics need. Now logically, we will be add one more if statement in Log api to support this new feature. This is where we broke the OCP, that is in order to add a new feature we have to change the existing functional code in the Log api which was well tested. What are the side-effects of this?
- More hard dependencies.
- Prone to bugs.
- Difficult to maintain code.
package working.example.tech.SOLID.OCP;
public class Logger {
enum LoggerType {
WORKINGEXAMPLE_DB_LOGGER,
WORKINGEXAMPLE_FILESYSTEMLOGGER,
WORKINGEXAMPLE_NOSQL_DBLOGGER
}
public Logger() {
}
public void Log(Object obj, LoggerType loggerType, String data) {
if (loggerType == LoggerType.WORKINGEXAMPLE_DB_LOGGER) {
((DBLogger)obj).Log(data);
} else if(loggerType == LoggerType.WORKINGEXAMPLE_FILESYSTEMLOGGER) {
((FileSystemLogger)obj).Log(data);
} else if(loggerType == LoggerType.WORKINGEXAMPLE_NOSQL_DBLOGGER) {
((NoSqlDbLogger)obj).Log(data);
}
}
}
public class NoSqlDbLogger {
public void Log(String data) {}
}
public class FileSystemLogger {
public void Log(String data) {}
}
public class DBLogger {
public void Log(String data) {}
}
Design that follow OCP
Now how to fix it so that it follows OCP. Instead of Logging class depends on the concrete implementation, it can depend on the Abstract class or Interface where each of the Logging Module can inherit/implement. With this Logging class makes a contract which is loggingSubSystem. So without changing the Log Api from Logger class we will still be able to add new functionality to the overall system. What did we achieve by doing all this?
- Loose coupling
- Bug free code
- Easily mantainable code
package working.example.tech.SOLID.OCP;
public class Logger {
public void Log(LoggingSubSystem obj, String data) {
obj.Log(data);
}
}
public interface LoggingSubSystem {
public void Log(String in);
}
public class DBLogger implements LoggingSubSystem{
public void Log(String data) {}
}
public class FileSystemLogger implements LoggingSubSystem{
public void Log(String data) {}
}
public class NoSqlDbLogger implements LoggingSubSystem{
public void Log(String data) {}
}
Conclusion
We have discussed OCP in this part with an example for better clarity on this, hope you will use this in your upcoming project. In the next blog I shall explain yet another principle. So stay tuned!