Чтобы было яснее
Страница 2. События и явные вызовы


 

События и явные вызовы

Вот еще один пример (несколько большего размера). Многие платформы поддерживают концепцию события как средства коммуникации между модулями. Предположим, у нас есть модуль обработки предварительных заказов, который при отмене заказа должен сделать так, чтобы модуль, отвечающий за работу с человеком, отправил оставившему заказ человеку соответствующее сообщение.

Мы можем реализовать это, использовав событие, как показано на рисунке 3. Для этого достаточно определить различные события для модуля предварительных заказов, и любой объект, который хочет выполнить некоторые действия при наступлении какого-либо события, может определить для него соответствующий обработчик. Этот подход выглядит привлекательным, так как нам не придется вносить изменения в класс Reservation, если мы захотим добавить какие-либо дополнительные действия при отмене предварительного заказа. Другие объекты тоже могут добавлять обработчики событий, поэтому вы сможете легко наращивать поведение в точках их обработки.

Рисунок 3. Отмена заказа с использованием событий (язык C#)

public delegate void ReservationHandler (IReservation source);
public class Reservation ...
    public String Id;
    public event ReservationHandler Cancelled;
        public Person client {
        get {
                return client;
                       }
        set {
                               value.AddReservation(this);
                       }
               }
    public void Cancel(){
        Cancelled (this);
       }

public class Person ...
    public String EmailAddress;
    public readonly ArrayList reservations;
   
    public void SendCancellationMessage(Reservation arg){
        //send a message
       }
   
    public void AddReservation(Reservation arg){
        //invoke SendCancellationMessage when the cancelled event occurs on arg
        arg.Cancelled +=
            new ReservationHandler(SendCancellationMessage);
       }

Рисунок 4. Явная реакция на отмену заказа (язык C#)

public class Reservation ...
    public String Id;
    public Person client;
    public void Cancel(){
       client.SendCancellationMessage(this);
       }

Однако за использование событий приходится платить: просматривая код соответствующего метода, я не могу сказать, что происходит при наступлении какого-либо события. Чтобы разобраться в происходящем, придется просмотреть весь остальной разрозненный код, создающий обработчики для этого события. Более ясный и понятный код для изложенной задачи (рисунок 4) четко показывает последовательность действий при отмене заказа. Однако в этом случае при изменении или добавлении поведения при обработке события придется модифицировать код класса Reservation.

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

Я не утверждаю, что события вообще нельзя использовать. Они позволяют наращивать поведение класса дополнительными действиями, не меняя сам класс. Это особенно удобно при работе с библиотечными классами, которые вы не можете изменить напрямую. События так же ценны тем, что не создают зависимости между классом, инициирующим наступление события, и классом, обрабатывающим его. Если эти классы находятся в различных пакетах, и вы не хотите делать пакеты зависимыми, отсутствие зависимости будет особенно полезно. Примером может послужить изменение содержимого окна на презентационном уровне при изменении объекта предметной области (модели). Механизм событий позволяет сделать это, сохранив исключительно важное разделение между моделью и ее презентацией.

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

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

 
« Предыдущая статья   Следующая статья »