SOLID Principles #2: Open/Closed Principle

Photo of Marcin Jakubowski

Marcin Jakubowski

Mar 9, 2018 • 6 min read

Welcome to the five-part series of blog posts about SOLID Principles. In each part I will describe and analyze one of these principles. In the last part, expect summary of the entire series containing a few tips and thoughts.

Let’s start. What are SOLID Principles? There are five general design principles of object-oriented programming intended to make software more understandable, extendable, maintainable and testable.

  1. Single responsibility principle (SRP)
  2. Open/closed principle (OCP)
  3. Liskov Substitution Principle (LSP)
  4. Interface Segregation Principle (ISP)
  5. Dependency Inversion Principle (DIP)

Today, more about second principle - Open/Closed Principle.

Open/Closed Principle

a class should be closed for modification but open for extensions.

In other words, we should extend class functionality without modifying its core behavior. We can achieve this by:

  • using inheritance mechanism
  • using composition technique
  • applying Dependency Injection pattern
  • applying Decorator pattern
  • applying Strategy pattern

Of course, these are not the only solutions.

The benefit is that we do not have to worry about the code that uses source classes because we did not modify them, so their behavior must be the same.

As for caveats, we should follow common sense. In practice, we must be careful to avoid creating too many derived classes, although, small modifications in classes are usually harmless. And that is the main reason why we should always write tests for our code - to notice the undesirable behavior of our code.


Now, consider the following example. Suppose that we wanted to write a service object for validating and updating user data, unfortunately, validation might vary depending on some conditions. A class of such kind would look like this:

class UserCreateService
  def initialize(params)
    @params = params
  def call
    return false unless valid?
  def valid?
    validator = assign_validator
  def assign_validator
    if some_condition
  def process_user_data

We even moved validation logic to separate classes but there is still problem with that code.

  • Adding new validation logic will be painful - another “if” condition or even “switch” statement
  • We must touch class that is not responsible for the validation because of validation logic change. It even sounds weird….
  • Testing will be harder - we should cover both processing & validation (many cases) logic.

Here is one the possible solutions:

class UserCreateService
  def initialize(validator:
    @validator = validator
  def call(params)
    return false unless validator.validate(params)
  attr_reader :validator
  def process_user_data

We have just passed our validator object to the service object via a constructor. What we used here is called Dependency Injection. If we choose a validator depending on some user’s attributes, we can create another class for deciding which class we should pick. If we choose a validator depending on context in which we are (various controllers etc.), we just pass the chosen validator to our service object.

Notice that at the same time we have fulfilled the Single Responsibility Principle (we moved extra responsibility to another class). Now, we do not have to modify the original class if we want to add another class for data validation. We just have to create a new validator class and pass it to an updating class in a desired place. That’s all.

The benefits are the same as in the case of the Single Responsibility Principle - code is cleaner, more maintainable and unit testing of such a solution will be much easier. We can test our updating service (just by mocking a validator object response) and validator classes in isolated environments. It is as simple as that.

Stay pragmatic

We made super flexible solution. We are ready to add new validation rule sets without pain. It cost us more work (and time) but we’re happy and proud. So what if you never have to add a new way of validation? Answer is obvious: we wasted time (and probably a money). The decision must be made by the developer based on the requirements, experience and probability of future changes.

There is no silver bullet. In my opinion there are no bad and good solutions. There could be only worse and better solutions.

My philosophy is to stay pragmatic and try to not overcomplicate things more than we have to.

I will touch this topic once again in the end of the whole series.

Photo of Marcin Jakubowski

More posts by this author

Marcin Jakubowski

Marcin graduated from Civil Engineering at the Poznań University of Technology. One year before...
How to build products fast?  We've just answered the question in our Digital Acceleration Editorial  Sign up to get access

We're Netguru!

At Netguru we specialize in designing, building, shipping and scaling beautiful, usable products with blazing-fast efficiency
Let's talk business!

Trusted by:

  • Vector-5
  • Babbel logo
  • Merc logo
  • Ikea logo
  • Volkswagen logo
  • UBS_Home