DEV Community

Carsten Behrens
Carsten Behrens

Posted on • Edited on • Originally published at carstenbehrens.com

SOLID: Open-Closed Principle in JavaScript

SOLID is an acronym referring to the SOLID Principles of class design that were popularized by Robert C. Martin.

Usually, these principles get applied to OOP languages that have classes. JavaScript is different in that it uses prototypal inheritance instead of classical inheritance. In the code samples, I will be using the ES6 class syntax because I prefer the new syntax over the ES5 syntax.

THE OPEN-CLOSED PRINCIPLE

A software artifact should be open for extension but closed for modification.

Bertrand Meyer

Or alternatively

You should be able to extend a classes behaviour, without modifying it.

Robert C. Martin

The goal of software architecture is to make sure that changes to the software are easy to make. The Open-Closed Principle (OCP) helps us with this goal in that it minimizes changes that have to be made to working code.

This goal is accomplished by partitioning the system into components, and arranging those components into a dependency hierarchy that protects higher-level components from changes in lower-level components.

Robert C. Martin

This means that we should design a class that is closed to modifications, meaning that it will not be changed, but also open, since new functionality can be added by extending this class.

Let's look at a simple example:

class DecimalToBinary {
  // Some other helper functions …

  dec2bin(number) {
    return parseInt(number, 10).toString(2);
  }
}
Enter fullscreen mode Exit fullscreen mode

Let's say we have a web app that needed the functionality to convert from decimal to binary numbers. We could implement a class like the DecimalToBinary class in the example above.

But what would happen if we suddenly need to also convert binary numbers to decimal numbers, or decimal to hexadecimal and so on?

To do so, we would most likely modify the DecimalToBinary class. This would violate the Open-Closed Principle.

A better way would be to anticipate this change when designing the class in the first place:

class NumberConverter {
  isNumber(number) {
    // Just an example of a helper function
    return true;
  }

  convertBase(number, fromBase, toBase) {
    // A naive implementation without error checking etc.
    return parseInt(number, fromBase).toString(toBase);
  }
}

class DecimalToBinary extends NumberConverter {
  isDecimalNumber(number) {
    // Just an example of a helper function, not actual implementation
    return true;
  }

  dec2bin(number) {
    return this.convertBase(number, 10, 2);
  }
}

class BinaryToDecimal extends NumberConverter {
  isBinaryNumber(number) {
    // Just an example of a helper function, not actual implementation
    return true;
  }

  bin2dec(number) {
    return this.convertBase(number, 2, 10);
  }
}
Enter fullscreen mode Exit fullscreen mode

Now you can see that we were able to extend the functionality of our program without modifying any old code.

This is obviously not the best example as this is a bit overkill and could also just be solved by a simple function, but it's still a good way to demonstrate the OCP.

OCP AT THE ARCHITECTURAL LEVEL

By using the OCP together with the Single Responsibility Principle to organize our components and the Dependency Inversion Principle to manage the dependencies you will naturally create a hierarchy of components.

Higher-level components in that hierarchy are protected from changes made to low-level components.

Top comments (1)

Collapse
 
dufrein profile image
Danila Shepelev

good material