Exploring SOLID : Open-Closed Principle

Rishabh Agarwal
4 min readAug 25, 2023

--

Photo by Evan Wise on Unsplash

“Open for Extension, Closed for Modification”

The letter ‘O’ in the SOLID principle stands for Open-Closed Principle. Philosophy behind the Open-Closed Principle (or OCP) helps us write better extensible code that is easier to work with.

Definition

Let us take a look at the formal definition first-

Software modules (classes, packages, functions, …) should be open for extensions but closed for modifications.

The above definition literally means that we should design our codebase in such a way that it is easier to add new features without making new changes to existing codebase.

Does that sound counter-intuitive? Adding new features without having to make modifications to the existing codebase!?

Although it may seem bizarre, it is absolutely feasible! We may implement the OCP principle using a variety of Object Oriented ideas, such as Interfaces, Polymorphism, and Inheritance.

Still confused? 🤔
Let us take an example to understand this principle…

Example

Consider you are working on an application that can convert any Word document to PDF. Let us think through a basic code outline for this application.

We will need to have classes representing Word files and PDF files. We will also have a Converter class whose main purpose will be to convert files from Word to PDF. This is how our code might look-

class WordFile {
string fileName;
int pageCount;
// Other class attributes...
public:
// Getters and Setters for the class attributes
char* getBinaryData() {} // Implementation Omitted
// Other methods
};
class PDFFile {
string fileName;
int pageCount;
// Other class attributes...
public:
// Getters and Setters for the class attributes
// Other methods
};
class FileConverter {
public:
PDFFile convert(const WordFile& wordFile) {
// Logic to convert WordFile to PDFFile
}
};

And our application functioned as intended!

Users of your programmes enjoyed it so much that they asked for the ability to convert PPTX files as well. And, this is how you updated the code to please your user.

// You added a new class to represent PPTX Files
class PPTXFile {
string fileName;
int pageCount;
// Other class attributes...
public:
// Getters and Setters for the class attributes
char* getBinaryData() {} // Implementation Omitted
// Other methods
};

// You modified the FileConverter Class
class FileConverter {
public:
PDFFile convertWord(const WordFile& wordFile) {
// Logic to convert WordFile to PDFFile
}
PDFFile convertPPTX(const PPTXFile& pptxFile) {
// Logic to convert PPTXFile to PDFFile
}
};

// Users would also have to modify the Calling Code

Again, your user continued to use the programme, and over time, they began to request support for a variety of file types. With every new file type, you had to modify your FileConverter class. And with every change to the Converter class, the user of the converter class will have to update its usages!

While you were able to add new features to your application you had to modify a lot of existing code. This leads to unnecessary changes and can lead to bugs! This is exactly where your code violates Open-Closed Principle!!

The Correct Way

Our code has some issues. Let us try to point some out —

  1. The business logic of converting a file type is specific to a file. Our existing code keeps it in the FileConverter which is semantically wrong! (Move conversion logic to File classes)
  2. Many fields across different File classes are the same. It is unnecessary to write them several times across all different classes. (Use a common parent class and employ Inheritance)
  3. To convert files, FileConverter has several distinct method names that are difficult to memorise. Additionally, it explains why whenever a new file type is added, the FileConverter class must always be updated. (Use Polymorphism)

With the problems (and their solutions) identified, we can go ahead and refactor our code as follows.

// Parent File Class
class File {
string fileName;
int pageCount;
// Other common attributes
public:
virtual char* getBinaryData() = 0;
virtual PDFFile convertToPDF() = 0;
};

class PDFFile : public File {
// Extra attributes
public:
PDFFile convertToPDF() {
return *this;
}
};

class WordFile : public File {
// Extra attributes
public:
PDFFile convertToPDF() {
// Conversion Logic for Word File Goes Here
}
};

class PPTXFile : public File {
// Extra attributes
public:
PDFFile convertToPDF() {
// Conversion Logic for PPTX File Goes Here
}
};

class FileConverter {
public:
PDFFile convert(const File& file) {
return file.convert();
}
};

With all the refactoring above, our code now follows the OCP. Adding any new file type requires creating a new class for that type and that is all! No more modifications to the existing code.

Conclusion

When attempting to make your code OCP compatible, OOP technologies like polymorphism, inheritance, and interfaces are quite effective. However, these are only tools, and correctly putting them to use is a skill that developers must acquire via practise.

Once mastered Open-Closed Principle can really make your software designs SOLID! 😉

--

--

Rishabh Agarwal

Software Developer 2 @ Schrödinger | IIT CSE Graduate - Writes about Software Engineering!