Как стать автором
Обновить

Управление зависимостями, события и паттерны Observer и Mediator

Время на прочтение3 мин
Количество просмотров8.8K
Паттерны в чистом виде встречаются довольно редко и при изучении паттернов, особенно на ранних этапах, важны не столько сами паттерны, сколько понимание механизмов (тактик), с помощью которых они реализуются. В этой статье я хотел бы описать один из таких механизмов (управление зависимостями), который используется в паттернах Observer и Mediator, но который часто упускается из внимания. Если ты только начинаешь изучать паттерны, то добро пожаловать под кат.

Управление зависимостями


Начнём с утверждения: если класс A зависит от класса B, то, не меняя поведения, можно переписать код так, что класс B будет зависеть от класса A или ввести ещё один класс C таким образом, что классы A и B будут независимы, а класс C будет связывать и зависеть от классов A и B.



Один класс зависит от другого, если он обращается к его полям или методам. Поля легко переносятся из одного класса в другой, поэтому остановимся подробнее на методах. Допустим, класс B содержит метод Print, который выводит текущую дату в консоль, а класс А вызывает этот метод.

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();
}

Таким образом класс A зависит от класса B. Чтобы вывернуть эту зависимость, вместо вызова метода Print напрямую, сгенерируем событие. Теперь класс А ничего не знает о классе B, а класс B может подписаться на событие класса A. Т.е. класс B будет зависеть от класса 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();
}

Поведение кода не меняется, а в вызывающем методе меняется только порядок инициализации объектов и передача зависимостей в конструктор.

По сути, это и есть реализация паттерна Observer в C#. Класс A — это обозреваемый объект (Observable), а класс B это обозреватель (Observer). Класс A является независимым классом, который генерирует уведомления (события). Другие классы, которые в этом заинтересованы, могут подписываться на эти события и выполнять свою логику. Система становится более динамичной за счёт того, что теперь классу A не нужно знать о других реализациях. Мы можем добавлять новые реализации, которые будут подписываться на события, при этом класс A будет оставаться неизменным.

Можно пойти дальше и убрать зависимость класса B от A, добавив внешний код, который будет связывать эти два класса, т.е. подписывать один объект на события другого.


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();
    }
}

Теперь классы A и B полностью независимы, каждый выполняет свою задачу и ничего не знает о других классах. Логика по взаимодействию между объектами уходит в новый класс. Только класс C знает в ответ на какие события и при каких условиях должны вызываться методы класса B. Таким образом, класс C становится медиатором.

Итоги: Борьба со сложностью системы


Одной из важных проблем в программировании является наличие сложных систем из запутанных классов с большим количеством зависимостей (сильносвязанные системы). Управляя зависимостями, можно уменьшить связанность, упростить систему и достичь большей динамичности и гибкости.

Паттерн Observer уменьшает связанность за счёт обращения зависимостей. Он хорошо применим, когда есть несколько источников событий и много слушателей, которые добавляются динамически. Другим хорошим примером использования этого паттерна является реактивное программирование, когда изменение состояния одного объекта приводит к изменению состояния всех зависимых от него объектов и так далее.



Паттерн Mediator уменьшает связанность системы за счёт того, что все зависимости уходят в один класс медиатор, а все остальные классы становятся независимы и отвечают только за логику, которую они выполняют. Таким образом, добавление новых классов становится проще, но с каждым новым классом логика медиатора сильно усложняется. С течением времени, если медиатор продолжает бесконтрольно разрастаться, то его становится очень тяжело поддерживать.


Опасным подводным камнем при использовании паттернов Observer и Mediator является наличие циклических ссылок, когда события из одного класса, проходя по цепочки объектов, приводят к генерированию этого же события ещё раз. Эта проблема тяжело разрешима и заметно усложняет использование паттернов.

Таким образом, в разных обстоятельствах, управляя зависимостями, можно прийти к разным паттернам, иногда к их смеси, а иногда этот механизм окажется полезен без использования паттернов вовсе. Боритесь со сложностью и не плодите сущностей.

image
Теги:
Хабы:
Всего голосов 9: ↑8 и ↓1+7
Комментарии1

Публикации

Истории

Работа

Ближайшие события