DEV Community

loading...
Cover image for Understanding SOLID Principles: Open Closed Principle

Understanding SOLID Principles: Open Closed Principle

tamerlang profile image Tamerlan Gudabayev Updated on ・5 min read

I know.

Your learning SOLID to be a better programmer.

But then you come across the second principle, and the first thing you read is:

Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.

I didn't understand shit, I bet you didn't either.

How is the code opened and closed at the same time? Young Uncle T asked himself this question 200 years ago, nothing made sense until Dad T came to him with the knowledge of the elders that have been passed from generation to generation. Fast forward 200 years later, it's 2020 and the open-closed principle is still being explained horribly. So without further ado, this is uncle T's take on the open-closed principle.

Table of Contents

  1. What is the open closed principle?
  2. Guidelines
  3. Example
  4. Conclusion
  5. References

What is the open closed principle?

The open-closed principle basically states that a software entity (class, module, function, etc.) should be open for extension but closed for modification. The main idea of this principle is to keep the existing code from breaking when you implement new features.

A class is:

  • Open if you can extend it, and produce a subclass and do whatever you want with it—add new methods or fields, override base behavior, etc.
  • Closed if it's 100% ready to be used by other classes—its interface is clearly defined and won’t be changed in the future.

I was very confused when I first heard this, because the words open & closed sound mutually exclusive. But a class can be both open (for extension) and closed (for modification) at the same time.

If a class is already developed, tested, reviewed, and used in an app, trying to mess with the code is very risky. Instead of changing the code of the class directly, you can simply create a subclass and override parts of the original class that you want to behave differently or you can extend the functionality and add your own methods. You'll achieve your goal but also won't break the existing functionality of the original class.

PS. This principle isn't meant to be applied to all changes. If you see a bug then go ahead and fix it; don't create a subclass for it. A subclass shouldn't be responsible for a parent's bugs.

Guidelines

The OCP is just a principle and not a generic solution. It describes what your entities should respect but does not provide a specific solution. In short there isn't a single way in respecting this principle. However there are some good general patterns that help us achieve this.

Program by Interface, not by Implementation

What this basically means is that you should try to write your code so it uses an abstraction (abstract class or interface) instead of the implementation directly.

Here's an example I got from stackoverflow that should explain how it works.

public enum Language
{
    English, German, Spanish
}

public class SpeakerFactory
{
    public static ISpeaker CreateSpeaker(Language language)
    {
        switch (language)
        {
            case Language.English:
                return new EnglishSpeaker();
            case Language.German:
                return new GermanSpeaker();
            case Language.Spanish:
                return new SpanishSpeaker();
            default:
                throw new ApplicationException("No speaker can speak such language");
        }
    }
}

[STAThread]
static void Main()
{
    //This is your client code.
    ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
    speaker.Speak();
    Console.ReadLine();
}

public interface ISpeaker
{
    void Speak();
}

public class EnglishSpeaker : ISpeaker
{
    public EnglishSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak English.");
    }

    #endregion
}

public class GermanSpeaker : ISpeaker
{
    public GermanSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak German.");
    }

    #endregion
}

public class SpanishSpeaker : ISpeaker
{
    public SpanishSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak Spanish.");
    }

    #endregion
}
Enter fullscreen mode Exit fullscreen mode

Example

For this example we are going to build a simple calculator, should be easy.

Our calculator should be able to do things as this IRL calculator.

Alt Text

First of all, let's define a top-level interface for our calculator

<?php 

interface CalculatorOperation {

} 
Enter fullscreen mode Exit fullscreen mode
<?php

public class Addition implements CalculatorOperation {
    private $left;
    private $right;
    private $result = 0.0;

    public __construct($left, $right) {
      $this->left = $left;
      $this->right = $right;
    }
}
Enter fullscreen mode Exit fullscreen mode

As of now, we only have an Addition class, we need to specify the other classes.

<?php 

public class Subtraction implements CalculatorOperation {
    private $left;
    private $right;
    private $result = 0.0;

    public __construct($left, $right) {
      $this->left = $left;
      $this->right = $right;
    }
}

public class Multiplication implements CalculatorOperation {
    private $left;
    private $right;
    private $result = 0.0;

    public __construct($left, $right) {
      $this->left = $left;
      $this->right = $right;
    }
}

public class Division implements CalculatorOperation {
    private $left;
    private $right;
    private $result = 0.0;

    public __construct($left, $right) {
      $this->left = $left;
      $this->right = $right;
    }
}
Enter fullscreen mode Exit fullscreen mode

Let's define our main class, which will perform our operations:

<?php

public class Calculator {
    private $operation;

    public __construct(CalculatorOperation $operation) {
    if(!$operation){
      throw new \Exception('Can not perform operation');
        }
        $this->operation = $operation;
    }

   public function calculate(){
      if($this->operation instanceOf Addition){
         $this->operation->result = $this->operation->left + $this->operation->right;
      } else if($this->operation instanceOf Subtraction){
     $this->operation->result = $this->operation->left - $this->operation->right;
      } else if($this->operation instanceOf Multiplication){
         $this->operation->result = $this->operation->left * $this->operation->right;
      } else if($this->operation instanceOf Divison){
         if($this->operation->right === 0){
           throw new \Exception("Can't divide by 0");
         }
         $this->operation->result = $this->operation->left / $this->operation->right;
      }
    return $this->operation->result;
   }
}

Enter fullscreen mode Exit fullscreen mode

So this works, but the implementation is absolutely horrendous. This does not follow the OCP whatsoever. Now let's imagine that after some time, our clients come and demand we make our calculator scientific:

Alt Text

To do that we have to create a class for each function and then we have to add more "if-else" code. But this is a horrible way because for every new functionality we have to modify the code in the calculate() method. So we need to extract this code and put it in an abstraction layer.

One solution would be to delegate each operation into their respective class:

<?php 

interface CalculatorOperation {
    public function perform();
} 
Enter fullscreen mode Exit fullscreen mode
<?php

public class Addition implements CalculatorOperation {
    private $left;
    private $right;
    private $result = 0.0;

    public __construct($left, $right) {
      $this->left = $left;
      $this->right = $right;
    }

    public function perform(): void {
      $this->result = $this->left + $this->right;
    }
}

public class Subtraction implements CalculatorOperation {
    private $left;
    private $right;
    private $result = 0.0;

    public __construct($left, $right) {
      $this->left = $left;
      $this->right = $right;
    }

    public function perform(): void {
      $this->result = $this->left - $this->right;
    }
}

public class Multiplication implements CalculatorOperation {
    private $left;
    private $right;
    private $result = 0.0;

    public __construct($left, $right) {
      $this->left = $left;
      $this->right = $right;
    }

    public function perform(): void {
      $this->result = $this->left * $this->right;
    }
}

public class Divison implements CalculatorOperation {
    private $left;
    private $right;
    private $result = 0.0;

    public __construct($left, $right) {
      $this->left = $left;
      $this->right = $right;
    }

    public function perform(): void {
      if($this->right === 0){
           throw new \Exception("Can't divide by 0");
        }
      $this->result = $this->left / $this->right;
    }
}
Enter fullscreen mode Exit fullscreen mode
<?php

public class Calculator { 
    public __construct(CalculatorOperation $operation) {
    if(!$operation){
      throw new \Exception('Can not perform operation');
        }

        $operation->perform();
    }

Enter fullscreen mode Exit fullscreen mode

So now our Calculator class is closed for modifications but open for extensions, and if we want to add more functionality then we would simply create a new class that implements the CalculatorOperation interface.

Conclusion

So let's wrap it up, I hope you at least got a better idea of the open-closed principle. It's simple once you get it or you get the "AHAAAH" moment. I would appreciate it if you left your feedback on the post because I'm still new to this. Have a nice day, and Uncle T out.

References

Discussion (1)

pic
Editor guide
Collapse
akachbat profile image
Adil Kachbat

Very clear explained, thanks.