📜 ⬆️ ⬇️

Dependency management, events and patterns Observer and Mediator

Pure patterns are quite rare and when studying patterns, especially in the early stages, it is not so much the patterns themselves that are important, but the understanding of the mechanisms (tactics) by which they are realized. In this article, I would like to describe one of these mechanisms (dependency management), which is used in Observer and Mediator patterns, but which is often overlooked. If you are just starting to learn patterns, then welcome under cat.

Dependency management


Let's start with the statement: if class A depends on class B, then, without changing the behavior, you can rewrite the code so that class B will depend on class A or introduce another class C in such a way that classes A and B are independent and class C will bind and depend on classes A and B.


')
One class depends on the other if it refers to its fields or methods. Fields are easily transferred from one class to another, so let's dwell on methods. Suppose class B contains the Print method, which prints the current date to the console, and class A calls this method.

class A { private readonly B _b; public A(B b) { _b = b; } public void Run() { _b.Print(); } } class B { public void Print() { Console.WriteLine(DateTime.Now.ToString()); } } public void Test() { var b = new B(); var a = new A(b); a.Run(); } 

Thus, class A depends on class B. In order to reverse this dependency, instead of calling the Print method directly, we generate an event. Now class A knows nothing about class B, and class B can subscribe to an event of class A. That is, class B will depend on class A.

 class A { public event EventHandler PrintRequested; public void Run() { PrintRequested.Invoke(this, EventArgs.Empty); } } class B { private readonly A _a; public B(A a) { _a = a; _a.PrintRequested += (s, e) => Print(); } public void Print() { Console.WriteLine(DateTime.Now.ToString()); } } public void Test() { var a = new A(); var b = new B(a); a.Run(); } 

The behavior of the code does not change, and only the order of initialization of objects and the transfer of dependencies to the constructor change in the calling method.

In essence, this is the implementation of the Observer pattern in C #. Class A is the Observable object, and Class B is the Observer. Class A is an independent class that generates notifications (events). Other classes that are interested in this can subscribe to these events and execute their logic. The system becomes more dynamic due to the fact that now class A does not need to know about other implementations. We can add new implementations that will subscribe to events, while class A will remain unchanged.

You can go ahead and remove the dependence of class B on A by adding external code that will link these two classes, i.e. sign one object on another's events.

 class A { public event EventHandler PrintRequested; public void Run() { PrintRequested.Invoke(this, EventArgs.Empty); } } class B { public void Print() { Console.WriteLine(DateTime.Now.ToString()); } } class C { public void Test() { var a = new A(); var b = new B(); a.PrintRequested += (s, e) => b.Print(); a.Run(); } } 

Now classes A and B are completely independent, each performing its own task and does not know anything about other classes. The logic of the interaction between objects goes into a new class. Only class C knows in response to which events and under what conditions class B methods should be called. Thus, class C becomes a mediator .

Results: Fighting system complexity


One of the important problems in programming is the presence of complex systems from entangled classes with a large number of dependencies (tightly coupled systems). By managing dependencies, you can reduce connectivity, simplify the system, and achieve greater agility and flexibility.

The Observer pattern reduces connectivity by reversing dependencies. It is well applicable when there are several sources of events and many listeners that are added dynamically. Another good example of using this pattern is reactive programming , when a change in the state of a single object leads to a change in the state of all objects dependent on it, and so on.



The Mediator pattern reduces the coherence of the system due to the fact that all dependencies go into one class of mediator, and all other classes become independent and are responsible only for the logic that they perform. Thus, adding new classes becomes easier, but with each new class the logic of the mediator becomes much more complicated. Over time, if the mediator continues to grow uncontrollably, then it becomes very difficult to maintain.


A dangerous pitfall when using Observer and Mediator patterns is the presence of circular references, when events from the same class, passing through a chain of objects, lead to the generation of the same event again. This problem is difficult to solve and makes the use of patterns much more difficult.

Thus, in different circumstances, by managing dependencies, you can come to different patterns, sometimes to a mixture of them, and sometimes this mechanism will be useful without using patterns at all. Fight with complexity and do not produce fruits.

image

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


All Articles