
Dependency Management, Events, and Observer and Mediator Patterns
In their pure form, patterns are quite rare, and when studying patterns, especially in the early stages, it is not so much the patterns themselves that are important that the understanding of the mechanisms (tactics) with which they are implemented. In this article, I would like to describe one of these mechanisms (dependency management), which is used in the Observer and Mediator patterns, but which is often overlooked. If you are just starting to learn patterns, then welcome to cat.
We 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 depends on class A or introduce another class C so that classes A and B are independent, and class C will bind and depend on classes A and B.

One class depends on another if it refers to its fields or methods. Fields are easily transferred from one class to another, so we will dwell on the methods in more detail. Suppose class B contains the Print method, which prints the current date to the console, and class A calls this method.
Thus, class A depends on class B. To reverse this dependency, instead of calling the Print method directly, we will generate an event. Now class A knows nothing about class B, and class B can subscribe to an event of class A. I.e. class B will depend on class A.
The behavior of the code does not change, and in the calling method only the order of initialization of objects and the transfer of dependencies to the constructor are changed.
In fact, this is the implementation of the Observer pattern in C #. Class A is an Observable, and Class B is an Observer. Class A is an independent class that generates notifications (events). Other classes that are interested in this can subscribe to these events and carry out 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 further and remove the dependence of class B on A by adding external code that will link the two classes, i.e. subscribe one object to the events of another.
Now the classes A and B are completely independent, each performs its task and does not know anything about the other classes. The logic for the interaction between objects goes into a new class. Only class C knows in response to what events and under what conditions class B methods should be called. Thus, class C becomes a mediator .
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 the use of this pattern is reactive programming , when a change in the state of one object leads to a change in the state of all objects dependent on it, and so on. Mediator

patternreduces the connectedness of the system due to the fact that all the dependencies go into one class, the mediator, and all other classes become independent and are responsible only for the logic that they perform. Thus, the addition of new classes becomes easier, but with each new class the mediator logic is greatly complicated. Over time, if the mediator continues to grow uncontrollably, then it becomes very difficult to maintain.

A dangerous pitfall when using the 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 significantly complicates the use of patterns.
Thus, in different circumstances, 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 complexity and do not produce entities.

Dependency management
We 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 depends on class A or introduce another class C so that classes A and B are independent, and class C will bind and depend on classes A and B.

One class depends on another if it refers to its fields or methods. Fields are easily transferred from one class to another, so we will dwell on the methods in more detail. 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. To reverse this dependency, instead of calling the Print method directly, we will generate an event. Now class A knows nothing about class B, and class B can subscribe to an event of class A. I.e. 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 in the calling method only the order of initialization of objects and the transfer of dependencies to the constructor are changed.
In fact, this is the implementation of the Observer pattern in C #. Class A is an Observable, and Class B is an Observer. Class A is an independent class that generates notifications (events). Other classes that are interested in this can subscribe to these events and carry out 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 further and remove the dependence of class B on A by adding external code that will link the two classes, i.e. subscribe one object to the events of another.
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 the classes A and B are completely independent, each performs its task and does not know anything about the other classes. The logic for the interaction between objects goes into a new class. Only class C knows in response to what events and under what conditions class B methods should be called. Thus, class C becomes a mediator .
Summary: 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 the use of this pattern is reactive programming , when a change in the state of one object leads to a change in the state of all objects dependent on it, and so on. Mediator

patternreduces the connectedness of the system due to the fact that all the dependencies go into one class, the mediator, and all other classes become independent and are responsible only for the logic that they perform. Thus, the addition of new classes becomes easier, but with each new class the mediator logic is greatly complicated. Over time, if the mediator continues to grow uncontrollably, then it becomes very difficult to maintain.

A dangerous pitfall when using the 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 significantly complicates the use of patterns.
Thus, in different circumstances, 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 complexity and do not produce entities.
