39

As I have had it explained, the open/closed principle states that once written code should not be modified (aside from bug fixes). But if my business rules change shouldn't I modify the code implementing those changes? I suspect I'm not understanding something about how the principle because it doesn't make sense to me.

7
  • 1
    Related: Why do many software developers violate the open/closed principle?. I hope the accepted answer debunks the common misconception we see here in most of the answers: that following the OCP means not changing existing code. That's IMHO wrong - follwing the OCP means to design a component so it can be reused in several situations without the need for change afterwards.
    – Doc Brown
    Commented Jul 28, 2021 at 20:49
  • @DocBrown: The original open-closed principle as defined by Bertrand Meyer does indeed say that you shouldn't change existing code except for fixing bugs. Instead new behavior should be introduced through extensions to the exiting modules. Unfortunately Robert C. Martin have created a great deal of confusion through several contradictory attempts to "clarify" the principle. If you are referring to one of Matins version of the principle you should probably specify which version you are talking about.
    – JacquesB
    Commented Jun 22 at 12:49
  • @JacquesB: I read the passage in Bertrand Meyer's book (thanks for the link) at least 3 times and I still disagree with your interpretation. The only interpretation for me which makes sense is the one where the protagonist of the OCP is the designer of a component when he/she designs a component with certain extension points. Publishing a new version of some component can break backwards compatibility, but never breaks the OCP (as long as prior extension points aren't removed). ....
    – Doc Brown
    Commented Jun 24 at 16:18
  • ... Since Meyer is surely an experienced professor, I am sure his idea of the OCP was the one which made really sense, not the interpretation you are proposing which would actually forbid to publish new versions of components.
    – Doc Brown
    Commented Jun 24 at 16:20
  • @DocBrown components can be published as long as API is extended and not modified.
    – Basilevs
    Commented Jun 24 at 17:58

7 Answers 7

34

This is probably the hardest of the solid principles to explain. Let me try. Imagine you wrote an Invoice class that works perfectly and has no bugs. It makes a PDF of an invoice.

Then someone says they want an HTML invoice with links in it. You don't change any code in Invoice to satisfy this request. Instead, you make another class, HTMLInvoice, that does what they now want. You leverage inheritance so that you don't have to write a lot of duplicate code in HTMLInvoice.

Old code that was using the old Invoice isn't broken or really affected in any way. The new code can use HTMLInvoice. (If you also do Liskov Substitutability, the L of solid, you can give HTMLInvoice instances to existing code that's expecting Invoice instances.) Everyone lives happily ever after.

Invoice is closed to modification, open to extension. And you have to write Invoice properly in advance for this to work, btw.

10
  • 1
    If the business rules change there is no assumption of working perfectly with no bugs, so the open/close principle doesn't apply?
    – JeffO
    Commented Nov 17, 2010 at 20:06
  • 2
    @Jeff O I distinguish between fixing a bug (where the code didn't meet the original requirement and nobody wants it as it is) and changing the requirements. If I require PDFs and the code makes PDFs, there's no bug, even though I now want HTML (and usually people want HTML as well, not instead of.) Commented Nov 17, 2010 at 23:53
  • 2
    If I were to do this, I'd take the Invoice class rename it PDFInvoice, create HTMLInvoice and abstact common code into other objects and have a new Invoice interface which both PDFInvoice and HTMLInvoice inherit from. That way I don't have my HTML invoice generating inheriting PDF specific stuff. In this case I've modified the code fairly extensively (while maintaining the same external interface as much as possible). Have I violated the open/close principle? If so, why would following the principle be better? Commented Nov 18, 2010 at 1:32
  • 7
    @Winston - this is what I meant when I said you have to write Invoice properly. Ideally there was already a pretty abstract invoice and you inherited PDFInvoice expecting this. If not, you have to break the rule once to set yourself up to not break it in the future. Either way, predicting future changes is a huge part of all this - and that's the "catch and cut up an elephant" part of the recipe. Commented Nov 18, 2010 at 1:35
  • 3
    Your last statement is the most important. Open/closed is a design ideal--and you have to get the design right up front to achieve it. Not everything needs to satisfy open/closed, either, but it's a powerful tool if you can get there. Commented Sep 12, 2013 at 14:43
20

The answer by Kate Gregory is very good, but consider a different situation where a new requirement can be satisfied by a relatively small change in the existing Invoice class. For example, lets say a new field must be added to the Invoice PDF. According to OCP, we should still create a new subclass, even if the new field could be added in the existing implementation by changing a few lines of code.

In my understanding, OCP reflects the reality of the 80's and early 90's, where projects often did not even use version control, much less had automated regression tests or the benefit of sophisticated refactoring tools. OCP was an attempt to avoid the risk of breaking code that had been manually tested and put into production. Today, we have better ways to manage the risk of breaking working software (namely, version control systems, TDD and automated testing, and refactoring tools).

7
  • 3
    Yes, because in practice, it is not possible to create a class that can be extend to suit all possible futures, unless you make all methods protected (which sucks and also violates the YAGNI principle, which is much more inportant than the O/C dito). Commented Sep 12, 2013 at 14:51
  • "According to OCP, we should still create a new subclass, even if the new field could be added in the existing implementation by changing a few lines of code.": Really? Why not add new fields or new methods? The important point is that you are only adding (extending) and not changing what is already there.
    – Giorgio
    Commented May 12, 2014 at 14:25
  • 1
    I think the principle makes sense when dealing with standard libraries/frameworks. You don't want to open and modify well established pieces of code. Otherwise it's all about constant refactoring and test, test, test. Commented May 12, 2014 at 14:39
  • @Giorgio Sure, adding new fields or methods is what I would recommend, in most cases. But that's not extension, it's "modification"; the whole point of OCP is that code should be "closed for modification" (ie, no changes to the pre-existing source file) while being "open for extension"; and extension in OCP is achieved through implementation inheritance.
    – Rogério
    Commented May 12, 2014 at 14:53
  • @Rogério: Why do you define the border between extension and modification at the class level? Is there a particular reason for this? I would rather set it at the method level: changing a method changes the behaviour of your application, adding a (public) method extends its interface.
    – Giorgio
    Commented May 12, 2014 at 16:05
15

Have you read the The Open-Closed Principle article by Uncle Bob's pals at ObjectMentor? I think it's one of the better explanations out there.

There are many heuristics associated with object oriented design. For example, “all member variables should be private”, or “global variables should be avoided”, or “using run time type identification (RTTI) is dangerous”. What is the source of these heuristics? What makes them true? Are they always true? This column investigates the design principle that underlies these heuristics -- the open-closed principle.

As Ivar Jacobson said: “All systems change during their life cycles. This must be borne in mind when developing systems expected to last longer than the first version.” How can we create designs that are stable in the face of change and that will last longer than the first version? Bertrand Meyer gave us guidance as long ago as 1988 when he coined the now famous open-closed principle. To paraphrase him:

SOFTWARE ENTITIES (CLASSES, MODULES, FUNCTIONS, ETC.) SHOULD BE OPEN FOR EXTENSION, BUT CLOSED FOR MODIFICATION.

When a single change to a program results in a cascade of changes to dependent modules, that program exhibits the undesirable attributes that we have come to associate with “bad” design. The program becomes fragile, rigid, unpredictable and unreusable. The open-closed principle attacks this in a very straightforward way. It says that you should design modules that never change. When requirements change, you extend the behavior of such modules by adding new code, not by changing old code that already works.

Description

Modules that conform to the open-closed principle have two primary attributes.

  1. They are “Open For Extension”.
    This means that the behavior of the module can be extended. That we can make the module behave in new and different ways as the requirements of the application change, or to meet the needs of new applications.
  2. They are “Closed for Modification”.
    The source code of such a module is inviolate. No one is allowed to make source code changes to it.

It would seem that these two attributes are at odds with each other. The normal way to extend the behavior of a module is to make changes to that module. A module that cannot be changed is normally thought to have a fixed behavior. How can these two opposing attributes be resolved?

Abstraction is the Key...

8
  • 3
    This is a good article explaining abstraction. There is a fundamental point to be considered, though, and that is was a good abstract design laid out in the first place? Many shops have a lot of legacy code that the only way to change it is "modification", not "extension". If this is the case, then one should probably work to change that, but until it does, you're stuck modifying code.
    – Michael K
    Commented Nov 17, 2010 at 16:13
  • 1
    @Chris, cool - I also recommend the "Clean code" book by Uncle Bob if you like this sort of thing. Commented Nov 17, 2010 at 16:26
  • @Michael - Totally agree, it's almost like the having to refactor the code to make it testable ideal. Commented Nov 17, 2010 at 16:26
  • The article demonstrates the importance of abstraction very nicely. But I'm not grasping the connection between abstractions and trying never to modify modules after I write them. The abstraction means that I can modify module X without having the make modifications to module Y. But isn't the point of doing that so I can modify module X or module Y if I need to? Commented Nov 18, 2010 at 1:34
  • 3
    Wow. Code is inviolate? I've never been a big fan of Uncle Bob. This principal is pedantic, extremely non-pragmatic, and has limited connection to reality.
    – user949300
    Commented Apr 13, 2014 at 5:23
9

Personally I think this principle should be taken with a pinch of salt. Code is organic, businesses change and code changes according to the needs of a business as time goes on.

I find it very difficult to get my head around the fact that abstraction is key. What if the abstraction was originally wrong? What if the business function has changed significantly?

This principle essentially ensures that ORIGINAL intentions and behavior of a design must never change. That probably works for those who have public API's and their clients have trouble keeping up with new releases, and some other edge-cases. However, if a company owns ALL the code, then I challenge this principle.

Having good test coverage of your code should make refactoring your code base a breeze. It means that it's okay to get stuff wrong - your tests will help guide you to a better design.

Saying that, if there aren't any tests, then this principle is sound.

1
  • Pinch -> shovel.
    – gnasher729
    Commented Jul 29, 2021 at 9:14
1

Your understanding is correct. In the original version of the principle (coined by OOP pioneer Bertrand Meyer), you don't modify existing ("published") code except for bug-fixes. All changes are introduces in the form of extensions, e.g. by adding a sub-class which override the behavior you want to change in the base class. So you end up with classes implementing both the old and the new behavior.

Pros: Very low-risk. Adding new functionality will never require rewriting any existing code and will not cause any existing code to change behavior.

Cons: You will have to keep old, obsolete code around forever and end up with deep gnarly class-hierarchies resembling the archeological layers of an ancient city. Also, you basically have to be able to predict the future in order to design the necessary extension points before the need to extend arise.

The principle itself is easy to understand - the tricky part is to understand when to use it. It is definitely not a good principle to follow for the typical modern application development, but there are certain specific scenarios where it makes sense.

Take a widely distributed library like the Java Class Library. If Oracle decided to significantly change the interface and behavior of a commonly used class like Vector it would break millions of software projects. Much better to introduce a new separate subclass with the desired changes and allow clients to decide if and when they will change their own code to use the new class.

But in the typical application development where a class is used only in the same code-base where it is defined, it is usually better to just update the class itself and adapt surrounding code if necessary. Especially if you have good test coverage. But you might want the selective apply the principle if you have a large complex code base, insufficient automated testing, and the class in question is used in many places.

A major problem with the open-closed principle is it is often not possible to apply the desired changes through extension. It requires that the base-class was designed from the beginning to have have the appropriate protected methods or other extension hooks to support the modification we need. But the reality of software development is we rarely can predict what changes might be needed in the future. "Designing for extensibility" often just lead to overly complicated code which still doesn't support the extensions we turn out to actually need. Usually it is much better to follow the YAGNI principle which says to only add extensibility when you actually need it.

1
  • Gotta love the tradition :)
    – Basilevs
    Commented Jun 24 at 17:55
0

Originally answered here;

Purpose of the Open closed Principle in SOLID Principles is to

  1. reduce the cost of a business change requirement.
  2. reduce testing of existing code.

Open Closed Principle states that we should try not to alter existing code while adding new functionalities. It basically means that existing code should be open for extension and closed for modification (unless there is a bug in existing code).

Let me explain this by taking AppLogger util class (written in Kotlin).

Let's say we have a requirement to log application wide errors to a online tool called Firebase. So we create below class and use it in 1000s of places to log API errors, out of memory errors etc.

open class AppLogger {

    open fun logError(message: String) {
        // reporting error to Firebase
        FirebaseAnalytics.logException(message)
    }
}

Let's say after sometime, we add Payment Feature to the app and there is a new requirement which states that only for Payment related errors we have to use a new reporting tool called Instabug, while continuing to report errors to Firebase just like before for all features including Payment.

Now we can achieve this by putting an if else condition inside our existing method

fun logger(message: String, origin: String) {
    if (origin == "Payment") {
        //report to both Firebase and Instabug
        FirebaseAnalytics.logException(message)
        InstaBug.logException(message)
    } else {
        // otherwise report only to Firebase
        FirebaseAnalytics.logException(message)
    }
}

Problem with this approach is that it violates Single Responsibility Principle which states that a method should do only one thing. Another way of putting it is a method should have only one reason to change. With this approach there are two reasons for this method to change (if & else blocks).

A better approach would be to create a new Logger class by inheriting the existing Logger class like below.

class InstaBugLogger : AppLogger() {

    override fun logError(message: String) {
        super.logError(message) // This uses AppLogger.logError to report to Firebase.
        InstaBug.logException(message) //Reporting to Instabug
    }
}

Now all we have to do is use InstaBugLogger.logError() in Payment features to log errors to both Instabug and Firebase. This way we reduce/isolate the testing of new error reporting requirement to only Payment feature as code changes are done only in Payment Feature. The rest of the application features need not be tested as there are no code changes done to the existing Logger.

0

“Once written, source code should not be modified” - this is totally wrong.

“If you write a class today, and you anticipate that in the future you will need instances with different behaviour, then it is a great idea to write your code today so that different behaviour can be achieved without source code changes” - this is excellent advice.

Why would you not do this? There is the YAGNI principle - you ain’t gonna need it. Preparing your source code for a situation that is theoretically possible but never actually happens is a waste of time. If you are sure you need it in version 3 of your software but version 1 needs to be released as soon as possible to make money now, that will be annoying. When you start work on version 3 then you first change your code to the way it should have been written in the first place, and then adapt some instances to behave in a different way. If you look at it solely as a developer then what you did was wrong. From a business point of view it was the right thing to do. Anyway, you do change the source code, and it’s the right thing to do.

And of course there will be bug fixes, work to adapt to changed libraries and so on. Plenty of situations where you change the source code because it is the right thing to do.

Not the answer you're looking for? Browse other questions tagged or ask your own question.