Good OCP Examples in Open Source Libraries

There was a lot of discussion on the topic "Open Closed Principle" on stackoverflow. However, it seems that a more relaxed interpretation of the principle usually prevails, therefore, for example, Eclipse is open for modification through plugins.

In accordance with strict OCP, you should only modify the source code to fix errors, and not to add new behavior.

Are there any good examples of strict interpretation of OCP in public or OS libraries where you can observe the evolution of a function through OCP: there is a Foo class with the bar method (), and there is a FooDoingAlsoX with foo2 () method in the next version of the library where the original class was extended where the source code has not been changed.

EDIT: According to Robert C. Martin: β€œThe binary executable version of the module, whether it is a link library, DLL or Java.jar, remains untouched” *. I have never seen libraries closed, in practice, new behavior is added to the library and a new version is published. According to OCP, the new behavior refers to the new binary module.

* Agile Software Development, Principles, Patterns and Practices, Robert C. Martin

+6
source share
3 answers

The principle of OCP states that a class must be open for extension, but closed for changes. The key to achieving this is abstraction. If you also read the DIP principle, you will find that abstractions should not depend on details, but details should depend on abstractions. In your example, you have details in your interface (two specific methods bar () and foo2 ()). In order to fully implement OCP, you should try to avoid such details (and, for example, try to move them as an abstraction and instead have one common foo method with different implementations).

For example, take a look at this interface in SolrNet: https://github.com/mausch/SolrNet/blob/master/SolrNet/ISolrCommand.cs This is a general command that says only that the command can be executed, but it does not give more details .

Details instead are interface implementations: https://github.com/mausch/SolrNet/tree/master/SolrNet/Commands

As you can see, you can add as many commands as you want without changing the implementation of any other class. Specific implementations can be considered closed to modifications, but the interface allows us to expand functionality with the help of new commands and, thus, is open for expansion.

(SolrNet is in no way extraordinary, I just used the examples from this project because when I read this post I have it in my browser, almost all well-established OO projects use the OCP principle in one way or another)

EDIT: If you need examples of this at the binary level, you can, for example, take a look at nopCommerce (http://nopcommerce.codeplex.com/releases/view/69081), where you, for example, can add your own delivery of providers, providers payments or exchange rate providers, without even affecting the original DLL, implementing a set of interfaces. And again, this is not something unusual with nopCommerce, it was only the first project that came to mind because I used it a couple of days ago;)

OCP is not a principle that should be used only at the binary level, but a good OOD uses OCP, not everywhere, but at all levels where it fits;) A "strict" OCP at the binary level does not always work and will add an additional level of complexity if you used it in every single situation, it is mainly interesting in situations where you want to change the implementation at runtime or when you want external developers to extend your interfaces. You should always consider the principle of OCP when you access your interfaces, but you will not see it as a law, but as a principle that should be used in the right situations.

I think you are referring to Agile Principles, Patterns and Practices, when you quote Robert C Martin, if so, also read the conclusion in the same chapter, where he talks about the same as above. If, for example, you read his book, Clean Code, it gives a more detailed explanation of the OCP principle, and I would say that the above quote is a little unsuccessful, because it allows people to think that you always put new code in a new DLL: s , JAR: s or libs, when the truth is that you will always consider the context.

I think you should rather look at Martins for a more recent OCP document http://objectmentor.com/resources/articles/ocp.pdf (which he also mentions in his later Clean Code book), there he never refers to individual binary files; rather, it refers to "classes, modules, functions." I think this proves that Martin means not only the binary extension when talking about OCP, but also extends classes and functions, so the binary extension is not more "strict" than the class extension in my first example.

+3
source

I don't know any really good examples, but I think there might be a reason for a more "relaxed interpretation" (like here on SO):

In order to fully implement the OCP principle in a real-world project, you need to communicate through scarce interfaces (see ISP and DIP for this) and Injection Dependency (both a property and a constructor) ... otherwise you are really fast either stuck or need to resort to a "relaxed interpretation" ...

Some interesting links in this regard:

+1
source

Background

On page 100 PPP Robert Martin says

"Closed for modification"
Extension of the module behavior does not lead to changes in the source or binary code of the module. The binary executable version of the module, whether it is a link library, DLL or Java.jar, remains untouched.

Also on page 103, he discusses an example written in C, where a design without OCP leads to recompilation of existing classes:

Thus, we not only need to change the source code of all witch / case statements or else chains, but we also need to change the binaries (by recompiling) of all modules that use any Shape data structure. Modifying binary files means that all DLLs, shared libraries, or other types of binary components must be redistributed.

It’s good to remember that this book was published in 2003, and many examples use C ++, which is a language known for long compilation periods (if header file handlers are not processed well - Remedy developers mentioned in one presentation that Alan Wake is complete assembly takes only about 2 minutes).

Therefore, when discussing binary compatibility on a small scale (i.e. within a single project), one advantage of OCP (and DIP) is faster compilation time, which is less true for modern languages ​​and machines. But on a large scale, when the library is used by many other projects, especially if their code is not under our control, the benefits of having to release new versions of the software still apply.

Example

As an example of an open source library that follows OCP in binary compatibility, take a look at JUnit. There are dozens of test frameworks that rely on JUnit @RunWith and Runner annotations to run them with the JUnit test runner - without having to change JUnit, Maven, IDE, etc.

Also recently added JUnit @ Annotations rules allows test scripts to connect to JUnit standard user behavior that would previously require a special test runner, Another example of OCP at the library level.

For comparison, TestNG does not comply with OCP, but it contains special JUnit checks to run TestNG and JUnit tests differently. A representative line can be found from the TestRunner.run () method:

if(test.isJUnit()) { privateRunJUnit(test); } else { privateRun(test); } 

As a result, even the tough TestNG tester has more features in some aspects (for example, supports parallel parallel tests), other test environments do not use it, because it is not expanded to support other testing systems without changing TestNG, (TestNG has a way to connect custom test runners , using - testrunfactory , but AFAIK allows you to use only one type of runner for each package.Thus , unlike JUnit, in one project it would be impossible to use many different rk testing.

Conclusion

However, in most cases, OCP is used in an application or library, in which case the base module and its extensions are packed inside the same binary file. In this situation, OCP is used to improve source code support, and not to prevent redeployment and new releases. The possible benefit of having to recompile an immutable file still exists, but since compilation time is so small in most modern languages, this is not very important.

The thing should always be kept in mind that after OCP is expensive, as this makes the system more complex. Robert Martin talks about this on PPP p. 105 and the completion of the chapter. OCP should be used with caution, only for the most likely changes. You should not proactively introduce hooks in line with OCP, but you should only insert hooks after this happens if necessary. Thus, it is hardly possible to find a project in which all new functions would be added without changing existing classes - unless someone does this as an academic exercise (my intuition says that it will be very difficult and the resulting code will not be clean) .

+1
source

Source: https://habr.com/ru/post/896082/


All Articles