So the principle is that
program entities (classes, modules, functions, etc.) should be open for expansion, but closed for changing
So, it is clear that we are talking about:
- Grades
- Modules
- Functions
- etc.
All of them should be open for expansion, but closed for modification. It sounds great, but to understand this principle by its name is quite difficult.
Let's start with "closed for modification". This means that the only reason why you can change the code of a class \ function \ module is to directly change the function embedded in it. Everything. There are no more reasons to change this code. It is at this point that the intersection in the principle of uniqueness of responsibility occurs.
Next, what does open to extension mean? This means that if you need your class \ function \ module to perform inherent functions in the new environment - they must support it without changing their code.
Let's, for clarity, consider an example.
Class Delegation Extension
Let's say we have a class to sort an array
public class BubbleSorter { public void Sort(int[] data) { int n = data.Length; for (int i = 0; i < n - 1; i++) for (int j = 0; j < n - i - 1; j++) if (data[j] > data[j + 1]) { int temp = data[j]; data[j] = data[j + 1]; data[j + 1] = temp; } } }
Let's look at this class. Let's try to understand what extension points it has. Extension points are those requirements that most likely will arise in your application (or have already arisen) in the course of different use cases of your code.
For example, we see that this method sorts only numbers. But with a probability of 99%, we will need to sort something else besides numbers. Also, sorting is only in ascending order, but, most likely, we will also need sorting in descending order. In fact, we again intersect with the principle of uniqueness of responsibility - we determine what responsibilities the class currently has and try to separate them. Let's use the existing interfaces in the data sheet and rewrite the code a bit:
public class BubbleSorter<T> { IComparer<T> _comparer; public BubbleSorter(IComparer<T> comparer) { _comparer = comparer; } public void Sort(T[] data) { int n = data.Length; for (int i = 0; i < n - 1; i++) for (int j = 0; j < n - i - 1; j++) if (_comparer.Compare(data[j], data[j + 1]) > 0) { var temp = data[j]; data[j] = data[j + 1]; data[j + 1] = temp; } } }
Now our sorter has become a bit more flexible. Now, in case we need to sort the rows instead of numbers, we don’t have to make a change to our sorter. If we need to sort in descending order rather than in ascending order, we will not have to change the sorter code. Thus, we can extend our class with new functionality, in essence, without changing the class itself.
The same is true for functions. For example,
var sorted = data.OrderBy(x=> /* ваше направление сортировки */ );
The same is true for software modules (for example, when you can reuse the same module in different scenarios).
However, the above does not mean that you need to immediately rush to your classes and begin to introduce such techniques into them. As always, before doing this, you need to think with your head. For example, you can see that the code above sorts only the arrays. and sorts in place (changes the original array). I deliberately left it, because I wanted to show that we should not go to extremes. Do not blindly follow the principle, it is necessary before you write something, think carefully. Because, if you follow the principle thoughtlessly, then at the end you can get too abstract code in which no one will understand anything.
In my case, I decided that I did not need to sort other data types, except arrays. I assumed that in the current project I would not need this (consider that this is part of a synthetic example).
But we considered only one of the options for expanding a class — transferring part of its responsibility to another object, which can be dynamically replaced. What else can we do with the class?
Class extension inheritance
Suppose we have a typical class writer in a CSV file
public class CsvWriter<T> { protected void WriteBody(IEnumerable<T> obj, TextWriter stream){ foreach(var ob in obj) stream.WriteLine(ob); } protected virtual void WriteInternal(IEnumerable<T> obj, TextWriter stream) { WriteBody(obj, stream); } public void Write(IEnumerable<T> obj, TextWriter stream) { WriteInternal(obj, stream); } }
Already with a trained eye, you can see that in this case the class itself decides how exactly the records of objects in the file will look. This is, of course, a candidate for separation into a separate responsibility, but this does not concern us now. But what worries is that our class does not write the file header. That is, he can write the column as it is, but the title of each column is not. What to do? From the signature of the functions of the class, you can see that it has a virtual method that fully determines the order in which data is written to the file. We can easily write a class heir and add a file header in it without changing the base class:
public class CsvWriterWithHeader<T> : CsvWriter<T> { protected virtual void WriteHeader(TextWriter stream){ stream.WriteLine("I AM HEADER!"); } protected override void WriteInternal(IEnumerable<T> obj, TextWriter stream) { WriteHeader(stream); WriteBody(obj, stream); } }
As a result, the functionality is expanded, the original class is not touched.
Class Aggregation Extension
So, for example, we have an interface and a class for the repository. Perhaps you even have several implementations of such a repository.
public interface IRepository { void SaveStuff(); } public class Repository : IRepository { public void SaveStuff() { // save stuff } }
And of course there is some kind of client for all of this.
class RepoClient { public void DoSomethig(IRepository repo) { //... repo.SaveStuff(); } }
And once the boss tells you that you need to log every call of every implementation of your repository. But I don’t want to change all implementations because of this (and you already know why). What to do? Of course roll a new implementation-wrapper
public class RepositoryLogDecorator : IRepository { public IRepository _inner; public RepositoryLogDecorator(IRepository inner) { _inner = inner; } public void SaveStuff() { // log enter to method try { _inner.SaveStuff(); } catch(Exception ex) { // log exception } // log exit to method } }
What she does? In essence, it accepts the object being decorated and proxies calls to it with logging. And if earlier your code looked like this:
var client = new RepoClient(); client.DoSomethig(new Repository());
Now it will look something like this:
var client = new RepoClient(); client.DoSomethig(new RepositoryLogDecorator(new Repository ()));
Have we changed our repository implementations? Not. Added functionality to them? Yes.
A little more info about these tricks with classes here
As a result, I want to note that we have touched on just a couple of options on how to extend the functionality without affecting the implementation. In fact, there are more options, it is important to understand the principle - you write a class \ function \ module, and they do only what they were created for and extend their functionality without affecting their source code. And do not forget about the principle of uniqueness of responsibility - as you should have already noticed, it is very dough related to the principle of openness / closeness.
Also, I repeat that it is necessary to have the flexibility of your data structures such as to ensure compliance with the project requirements, and not to generate excessive flexibility, since excessive flexibility complicates your abstractions for nothing.
If you have finished reading up to this point, then I will reveal to you a great secret - each of the methods above uses one or another pattern. Try to think what pattern where, go over the main patterns from the most famous pattern book (from the gang), think about which ones expand the class functionality, and then, I hope, the mosaic will start to take shape.
переопределение методов не должно ужесточать условия входных параметров и ослаблять условия выходных- confuse with L :) - tym32167