Книга: ЯЗЫК ПРОГРАММИРОВАНИЯ С# 2005 И ПЛАТФОРМА .NET 2.0. 3-е издание

Интерфейсы обратного вызова

Интерфейсы обратного вызова

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

Чтобы показать варианты использования интерфейсов обратного вызова, мы изменим уже знакомый нам тип Car так, чтобы он мог информировать вызывающую сторону о приближении поломки машины (т.е. о том, что текущая скорость на 10 км/ч ниже максимальной скорости) и о свершившейся поломке (когда текущая скорость равна или выше максимальной скорости). Способность посылать и принимать соответствующие события будет реализована с помощью интерфейса, носящего имя IEngineEvents.

// Интерфейс обратного вызова.
public interface IEngineEvents {
 void AboutToBlow(string msg);
 void Exploded (string msg);
}

Интерфейсы событий обычно реализуются не объектом, непосредственно "заинтересованным" в получении событий, а некоторым вспомогательным объектом, который называется объектом-приемником. Отправитель событий (в данном случае это тип Car) при определенных условиях выполняет для приемника соответствующие вызовы. Предположим, что класс приемника называется CarEventSink, и он просто выводит поступающие сообщения на консоль. Кроме того, наш приемник содержит строку, в которой указано его информативное имя.

// Приемник событий Car.
public class CarEventSink: IEngineEvents {
 private string name;
 public CarEventSink(){}
 public CarEventSink(string sinkName) { name = sinkName; }
 public void AboutToBlow(string msg) { Console.WriteLine("{0} сообщает: {1}", name, msg); }
 public void Exploded(string msg) { Console.WriteLine(" {0} сообщает: {1}", name, msg); }
}

Теперь, когда у нас есть объект-приемник, реализующий интерфейс событий, нашей следующей задачей является передача ссылки на этот приемник в тип Car. Тип Car будет хранить эту ссылку и при необходимости выполнять обратные вызовы приемника. Чтобы тип Car мог получить ссылку на приемник, нужно добавить в тип Car вспомогательный член, который мы назовем Advise(). Точно так же. если вызывающая сторона пожелает отменить привязку к источнику событий, она может вызвать другой вспомогательный метод типа Car – метод Unadvise(). Наконец, чтобы позволить вызывающей стороне регистрировать множество приемников событий (с целью групповой адресации), тип Car поддерживает ArrayList для представления исходящих соединений.

// Тип Car и вызывающая сторона могут связываться
// с помощью интерфейса IEngineEvents.
public class Car {
 // Набор связанных приемников.
 ArrayList clientSinks = new ArrayList();
 // Присоединение к источнику событий или отсоединение от него.
 public void Advise(IEngineEvents sink) {clientSinks.Add(sink);}
 public void Unadvise(IEngineEvents sink) {clientSinks.Remove(sink);}
 …
}

Чтобы на самом деде посылать события, мы обновим метод Car.Accelerate() так, чтобы он осуществлял "проход" по соединениям, указанным в ArrayList, и при необходимости выдавал подходящее сообщение (обратите внимание на то, что теперь в классе Car есть член-переменная carIsDead логического типа для представления состояния двигателя машины).

// Протокол событий на базе интерфейса.
class Car {
 …
 // Эта машина работает или нет?
 bool carIsDead;
 public void Accelerate(int delta) {
  // Если машина 'сломалась', отправить событие Exploded
  // каждому приемнику.
  if (carIsDead) {
   foreach(IEngineEvents e in clientSinks) e.Exploded("Извините, машина сломалась…");
  } else {
   currSpeed += delta;
   // Отправка события AboutToBlow.
   if (10 == maxSpeed – currSpeed) {
    foreach(IEngineEvents e in clientSinks) е.AboutToBlow("Осторожно! Могу сломаться!");
   }
   if (currSpeed ›= maxSpeed) carIsDead = true;
   else Console.WriteLine(" tCurrSpeed = {0} ", currSpeed);
  }
 }
}

Вот подходящий программный код клиента, в котором используется интерфейс обратного вызова для отслеживания событий Car.

// Создание машины и мониторинг событий.

public class CarApp {
 static void Main(string[] args) {
  Console.WriteLine("*** Интерфейсы и контроль событий ***");
  Car cl = new Car("SlugBug", 100, 10);
  // Создание объекта-приемника.
  CarEventSink sink = new CarEventSink();
  // Передача Car ссылки на приемник.
  cl.Advise(sink);
  // Ускорение (вызывает наступление событий).
  for (int i = 0; i ‹ 10; i++) cl.Accelerate(20);
  // Разрыв связи с источником событий.
  cl.Unadvise(sink);
  Console.ReadLine();
 }
}

На рис. 8.1 показан конечный результат работы этого основанного на интерфейсе протокола событий.


Рис. 8.1. Основанный на интерфейсе протокол событий

Обратите внимание на то, что метод Unadvise() позволяет вызывающей стороне селективно отключаться от источника событий. Здесь перед выходом из Main() вызывается Unadvise(), хотя, строго говоря, это и не обязательно. Но предположим, что приложение должно зарегистрировать два приемника, динамически отключить их по очереди в процессе выполнения, а затем продолжить работу.

static void Main(string[] args) {
 Console.WriteLine("***** Интерфейсы и контроль событий *****");
 Car cl = new Car("SlugBug", 100, 10);
 // Создание двух объектов.
 Console.WriteLine("***** Создание приемников *****");
 CarEventSink sink = new CarEventSink("Первый приемник");
 CarEventSink myOtherSink = new CarEventSink("Второй приемник");
 // Передача приемников объекту Car.
 Console.WriteLine("n***** Отправка приемников в Car *****");
 cl.Advise(sink);
 cl.Advise(myOtherSink);
 // Ускорение (при этом генерируются события).
 Console.WriteLine("n***** Ускорение *****");
 for (int i = 0; i ‹ 10; i++) cl.Accelerate(20);
 // Отключение первого приемника событий.
 Console.WriteLine("n***** Отключение первого приемника *****");
 cl.Unadvise(sink);
 // Новое ускорение (теперь вызывается только myOtherSink).
 Console.WriteLine("n***** Снова ускорение *****);
 for(int i = 0; i ‹ 10; i++) cl.Accelerate(20);
 // Отключение второго приемника событий.
 Console.WriteLine("n***** Отключение второго приемника *****");
 Console.ReadLine();
}

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

Исходный код. Проект EventInterface размещен в подкаталоге, соответствующем главе 8.

Оглавление книги


Генерация: 1.596. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз