Open closed Principle (OCP or O in SOLID principles) in C#
What is Open closed Principle?
In 1988 the open closed principle (OCP) mentioned by Bertrand Meyer as: Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
Software that works should when possible not be changed when your application is extended with new functionality.
Instead it should be possible to extend the existing software with new functionality without any modification to the current codebase and without adding duplicate code or duplicate functionality.
The OCP handles the way you want to structure your code in order to yield one of the greatest benefits claimed for object oriented technology: reusability and maintainability.
The best way to implement the open closed principle is to first start with implementing the Single Responsibility Principle: a class should have one, and only one, reason to change. This will separate different concerns in your code.
The next step is represent these separate concerns by abstractions and let consumers of these concerns talk to these abstractions.
To state the open closes principle very straightforward way you can say :
– 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.
Abstraction is the way to realize this principle.
Derivatives from an abstraction are closed for modification because the abstraction is fixed but behaviour can be extended by creating new derivatives of the abstraction.
Let’s continue this discussion with the new world of client, server and Web APIs. The open closed principle states “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”. For the sake of this post I will use the term “server” to describe a class in an API – a business object for example. “Client” will be used to describe the consumer of a service – it could be a user interface component for example. The client has a dependency on a server at some level, the server knows nothing about a client.
Let’s paraphrase what Bertrand Meyer said about this principle. I am paraphrasing his definition as stating that once a class is completed it could only be modified to fix errors. If you wanted to add any new features or change the behavior in any way you had to create a new class. Inheritance may be used to reuse code from the original class but the resulting interface of the new class need not be the same as the interface of the original class. Consider client class A that depends on server class B. Once A and B are working they would never change. Now we create server C which is an extension of B (reuses implementation but may have a different interface). We have extended the API by creating server C but we did not change A or B so A continues to behave correctly. The focus here is on how we can extend our API but keep clients working by not changing any of the working code.
Robert Martin is credited with popularizing the more modern application of the principle which advocates the use of abstract interfaces. With this approach, server implementations can be changed, and multiple server implementations can be created and substituted for each other in the client. Again consider client class A that depends on server class B but this time B is an abstract class. Now we create server class C which is a derivation of B. Since client A is dependent on an abstraction, we can extend the client by passing it C without changing A. The focus here is on opening the client to extension but closing the client to modification.
Meyer’s approach is better in that it shouldn’t be possible to break working client A by adding server derivation C. The down side, however, is that our answer to all change is to add more code, client A will not be able to make use of server extension C, and refactoring of B is not allowed. With Martin’s approach we are essentially saying “as long as we respect the interface, never mind what I do to the server”. The down side here is that it is easy to imagine breaking client A by adding a poorly designed server derivation C or even by carelessly refactoring B and inadvertently adding a bug. The upside is that with this approach, client A may be extended to make use of server C without changing the client and since we have the freedom to refactor our servers we should have less code, we can make use of new language features where it makes sense, and we can reorganize things as our API evolves. In short the resulting code should be easier to maintain.