The Open/Closed Principle (OCP) is the second principle of SOLID object-oriented design patterns outlined by Robert C. Martin (a.k.a. Uncle Bob) in his 2002 Agile Software Development, Principles, Patterns, and Practices. It states that software entities should be “open for extension but closed for modification.” (1)
The Open/Closed Principle provides a conceptual basis for maintaining flexibility as software evolves and grows. This means focusing on extensibility over modification. More concretely, extensible software entities are designed such that additional functionality can be added without requiring modification to other interrelated entities.
Non-Breaking Changes
As with many design principles, the OCP has a strong focus on abstraction. Particularly, the OCP is often achieved by using abstract base classes to factor out implementation specifics. Common code smells like identically-named methods among multiple classes often signify opportunities where implementing the OCP may be beneficial.
This approach allows extended functionality via concrete implementation classes without necessitating changes to base classes. In other words, a developer can add new functionality without changing the existing code of related functions, classes, and methods!
Example of OCP Violation
To exemplify the OCP, let us again consider a hypothetical stock trading application, continuing from the examples in the article about the Single Responsibility Principle (SRP) of SOLID design.
Previously, this example provided a single Buy and Sell as part of its Transaction class. This time, we’ll incorporate functionality to make a record of Buy
and Sell
transactions. After all, one needs something to show come tax season!
class Transaction { private void buy(String ticker, int quantity, float price){ Buy.execute(ticker, quantity, price); } private void sell(String ticker, int quantity, float price){ Sell.execute(ticker, quantity, price); } } class Buy{ // Create a record of the Buy transaction public static String makeRecord(String ticker, int quantity, float price, String date){ // Create a string containing relevant Buy metadata return MessageFormat.format("{0}-{1}-{2}-{3}", ticker, String.valueOf(quantity),String.valueOf(price),date); } // Class-specific Buy logic static void execute(String stock, int quantity, float price){ // Execute buy action here } } class Sell{ // Create a record of the Buy transaction public static String makeRecord(String ticker, int quantity, float price, String date){ // Create a string containing relevant Buy metadata return MessageFormat.format("{0}-{1}-{2}-{3}", ticker, String.valueOf(quantity),String.valueOf(price),date); } // Class-specific Sell logic static void execute(String stock, int quantity, float price){ // Execute buy action here } }
Notice that the Buy and Sell classes each implement their own version of the makeRecord
method. My nose recoils when copy/pasting blocks of code such as this, signifying potential code smell.
The design of this code violates the Open/Closed principle because anytime a record format needs to be adjusted it requires a change in both the Buy
and Sell
class. In other words, a single extension requires multiple modifications.
Example of OCP Adherence
One possible approach to approving the above code, with the Open/Closed Principle in mind, is to use inheritance. This dictates that child classes, inheriting from a parent class, should share similar functionality.
This approach allows the Buy
and Sell
methods to share a common method to create records of transactions. This allows the functionality of making records to be extended without requiring modification of either Buy or Sell. They just call the inherited makeRecord
method!
class RecordableTransaction { // Create a record, in a format used by all inherited classes public static String makeRecord(String ticker, int quantity, float price, String date){ // Create a string containing relevant Buy metadata return MessageFormat.format("{0}-{1}-{2}-{3}", ticker, String.valueOf(quantity),String.valueOf(price),date); } } class Transaction { // Still uses Buy class to execute buy transactions private void buy(String ticker, int quantity, float price){ Buy.execute(ticker, quantity, price); } // Still uses the Sell class to execute sell transactions private void sell(String ticker, int quantity, float price){ Sell.execute(ticker, quantity, price); } } class Buy extends RecordableTransaction{ // Class-specific Buy logic still implemented here static void execute(String stock, int quantity, float price){ // Execute buy action here } } class Sell extends RecordableTransaction{ // Class-specific Sell logic still implemented here static void execute(String stock, int quantity, float price){ // Execute sell action here } }
This version approaches the design by using a new Parent class named RecordableTransaction
that defines a makeRecord
method. The Buy
and Sell
classes, now children of the RecordableTransaction
class, both share the makeRecord
method and its implementation. The Open/Closed Principle UML diagram example below provides a visual of what is going on here:
This is one of many approaches to abstract common functionality in the hypothetical stock trading application. For another example, check out Joel Abrahamsson’s implementation of the Open/Closed Principle’s prototypical Shape example.
Final Thoughts
The SOLID framework for better object-oriented design has stood apart from other more transient models throughout the years. Its AGILE approach to designing better code helps avoid common mistakes that prevent software from being flexible, easily maintainable, and scalable.
The Open/Closed Principle is but one of the five class/module principles. Its guidance for class-based designs focused on ease-of-extensibility is revered among engineers, programmers, and hobbyist coders alike. Yes—you have to put on your thinking cap initially to understand its conceptual approach. Yes—it takes more time to implement. And Yes—sometimes the extra time is warranted for small-scale applications or rapid-prototyping.
As a closing thought, I will argue that even when implementing the OCP is not warranted one will still benefit from being aware of its potential use in development.
References
- Martin, Robert C. Agile Software Development, Principles, Patterns, and Practices. 1st ed., Pearson, 2002.