Open / Closed Principle

The Open/Closed Principle (OCP) states that all classes should be open for extension but closed for modification. “Open for extension” means that the design of your class should allow adding new functionality as new requirements are generated. “Closed for modification” means that once you have developed the class, you should never modify it, unless you need to fix bugs.

Even the two parts of the principle appear to be opposite, if you structure your code correctly, you should be able to add functionality without editing existing code. How you achieve this ? Your design should reply on abstractions for dependencies, such as interfaces or abstract classes, rather than using concrete implementation. New functionality can be added by adding new classes than implement the interfaces.

The first benefit of OCP to your code is that you don’t need to change the existing code, once it was written, tested, documented. So you reduce the risk on adding bugs into existing code. Another benefit of using contracts is loose coupling and increased flexibility.

ocpSource: Solid motivational pictures

Let’s consider the GoldPriceAlert class from previous article with the extra feature to allow user to decide the way to be notified: console, email, sms.

The above sample code is a basic module for logging alerts. As you can see if you decide to implement the SMS logging type, you need to modify the GoldPriceAlert method. This violates the OCP.

We can easily refactor the code to achieve the OCP by removing the enum LogType that limits the types of alerts. Then we’ll create a class for each type of logger. Additional log types can be added later without changing any existing code.

The logger class still performs all logging but using one of the classes described earlier. In order that the classes are loose coupled, each message logger type implements ILog interface. The GoldPriceAlert class is never aware of the type of the logger being used as the dependency is provided as an ILog instance using constructor injection.

The refactored code:

Single Responsibility Principle

Let’s start our journey on SOLID principles by describing the Single Responsibility Principle (SRP).

SRP states that each class should have one responsibility only and, therefore, only one reason to change. It means that every class should have one job to do or one single purpose. Of course, we still allow adding multiple methods/properties and members as long they relate to that single responsibility.

Applying SRP will change your existing code and the first impact will be at the class level: the classes in your projects will become smaller and cleaner. As a note, you shouldn’t be worried about changing the classes even you will notice the increased number of new classes. All major programming languages allows you to organize classes using namespaces and project folders. Keeping your code into classes tightly focused into a single purpose leads to code that is simpler to understand and maintain.

Having small and cohesive classes reduces the code fragility by decreasing the chance of a class to contain bugs (reducing the need for changes). As the classes will perform only one duty, multiple classes will work together to achieve larger tasks. It allows you to easy modify the features, either by extending existing classes or introducing interchangeable versions.

Along with the other principles, SRP allows you to achieve loose coupling.

srpSource: Solid motivational pictures


Let’s consider below code snippet that violates the SRP principle and then refactor it to comply with the principle.

The above class will read gold price from an independent trusted “source” and check if the price is under certain threshold and it will decide to alert me if I need to buy some gold.

There are at least few reasons to change the above class. Let’s consider at some point I need to add a new “trusted” independent source for gold price. Perhaps I want to change the way I’m evaluating the opportunity to buy gold. Or I would like a more sophisticated system to be notified instead of logging out to console.

To refactor the code, we’ll separate the functionality into three classes. The first is GoldMeter.cs that will retain GoldPrice property and ReadGoldPrice method as both are close related. The other methods are removed so that the only reason to change is the replacement of the “trusted” source price.

The second class is GoldPriceChecker.cs that will include a very simple method to compare the price with a certain acceptable value. The only reason to change the class is if the acceptable value is changed.

The final class GoldPriceAlert.cs will display the alert if a very simple way. GoldMeter class is dependency injected. The only reason to change the GoldPriceAlert class is if we decide to enhance the alert system.

The refactored code below breaks some SOLID principles just to ensure the SRP is visible. Further refactoring of the code is required to achieve SOLID compliance.

The Solid principles

The SOLID acronym was introduced by Robert Martin (also known as “Uncle Bob” and represents a list of five guidelines that can enhance the maintainability of the software.

Bad dependency management will make your code rigid, fragile, difficult to reuse. Rigid code is that which is difficult to modify, either to change existing functionality or add new features. Fragile code is susceptible to the introduction of bugs, particularly those that appear in a module when another code is changed.

If you follow SOLID principles, you can produce better and flexible code, more robust and with a higher reusability.

The 5 principles:

1. Single Responsibility Principle (SRP) – each class should have one responsibility and, therefore, only one reason to change.

2. Open / Closed Principle (OCP) – all classes and similar units of source code should be open for extension but close to modification.

3. Liskov Substitution Principle (LSP) – code that uses a base class must be able to substitute a subclass without knowing it.

4. Interface Segregation Principle (ISP) – the client should not be forced to depend upon interfaces that they do not use. Instead, those interfaces should be minimized.

5. Dependency Inversion Principle (DIP) – high level modules should depend upon low level modules and that abstractions should not depend upon details.