Тверской государственный университет
Опубликован: 02.12.2009 | Доступ: свободный | Студентов: 2375 / 262 | Оценка: 4.47 / 4.24 | Длительность: 14:45:00
Лекция 8:

Классы с событиями

< Лекция 7 || Лекция 8: 12345 || Лекция 9 >

Проект "Город и его службы"

Завершить лекцию хочется проектом, в котором собраны все рассматриваемые аспекты работы с событиями. Вернемся к моделированию жизни города, происходящих в нем событий, реакции на них городских служб. Наша главная цель в данном проекте еще раз показать, как возникающее событие, в данном случае - пожар в одном из домов города, обрабатывается по-разному городскими службами - пожарными, милицией, скорой помощью. В нашей модели сообщение о событии получает не одна, как ранее, а несколько служб. Событие имеет как входные, так и выходные аргументы, так что городу необходимо будет анализировать результаты работы каждой службы. Стоит обратить внимание и на организацию взаимодействия между классами, задающего город и его службы. Конечно, все упрощено, в реальном городе событиями являются не только пожары и преступления, но и более приятные ситуации: день города, открытие фестивалей и выставок, строительство новых театров и институтов.

Начнем с описания делегата, задающего событие "пожар" и построенного по всем правилам.

public delegate void FireEventHandler(object sender, FireEventArgs e);

А теперь рассмотрим свойства класса, определяющего новый город.

/// <summary>
   /// Модель города с событиями
    /// и следящих за ними службами города
   /// </summary>
   public class NewTown
   {
      //свойства
    string townName;    //название города
      int buildings;       //число домов в городе
      int days;         //число дней наблюдения
      //городские службы
      Police policeman;
      Ambulance ambulanceman;
      FireDetect fireman;
      //события в городе
      public event FireEventHandler Fire;
        string[] resultService;      //результаты действий служб      
      //моделирование случайных событий
      private Random rnd = new Random();
      //вероятность пожара в доме в текущий день
        double fireProbability;

В нашем городе есть дома; есть время, текущее день за днем; городские службы; событие "пожар", которое, к сожалению, может возникать с заданной вероятностью каждый день в каждом доме. Рассмотрим конструктор объектов нашего класса:

/// <summary>
      /// Конструктор города
        /// Создает службы и включает наблюдения
        /// за событиями
      /// </summary>
      /// <param name="name">название города</param>
      /// <param name="buildings">число домов</param>
      /// <param name="days">число дней наблюдения</param>
      public NewTown(string name, int buildings, int days)
      {
       townName = name;
       this.buildings = buildings;
         this.days = days;
       fireProbability = 1e-3;
            //Создание служб
         policeman = new Police(this);
         ambulanceman= new Ambulance(this);
         fireman= new FireDetect(this);
            //Подключение к наблюдению за событиями
         policeman.On();
         ambulanceman.On();
         fireman.On();      
      }

Конструктору передается имя города, число домов в нем и период времени, в течение которого будет моделироваться жизнь города. Конструктор создает службы города - объекты соответствующих классов Police, Ambulance, FireDetect, передавая им ссылку на сам объект "город". После создания служб вызываются их методы On, подключающие обработчики события Fire каждой из этих служб к событию.

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

/// <summary>
/// Зажигается событие.
/// Поочередно вызываются обработчики события  
/// </summary>
/// <param name="e">
/// входные и выходные аргументы события
/// </param>
protected virtual void OnFire(FireEventArgs e)
      {
    const string MESSAGE_FIRE =
                "В городе {0} пожар! Дом {1}. День {2}-й";
    Console.WriteLine(string.Format(MESSAGE_FIRE, townName,
        e.Building, e.Day));
    if (Fire != null)
    {
        Delegate[] eventHandlers = 
            Fire.GetInvocationList();
        resultService = new string[eventHandlers.Length];
        int k = 0;
        foreach (FireEventHandler evHandler in
            eventHandlers)
        {
            evHandler(this, e);
            resultService[k++] = e.Result;           
      }
    }
  }

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

Где и когда будет включаться событие Fire? Напишем метод, моделирующий жизнь города, где для каждого дома каждый день будет проверяться, а не возник ли пожар, и, если это случится, будет включено событие Fire:

/// <summary>
/// Моделирование жизни города
/// </summary>
  public void LifeOurTown()
{
      const string OK =
         "В городе {0} все спокойно! Пожаров не было.";
      bool wasFire = false;   
      for(int day = 1; day <= days; day++)
      for(int building = 1; building <= buildings; building++)
      {
              if (rnd.NextDouble() < fireProbability)
              {
                  FireEventArgs e = new FireEventArgs(building, day);
                  OnFire(e);
                  wasFire = true;
                  for (int i = 0; i < resultService.Length; i++)
                      Console.WriteLine(resultService[i]);
              }
      }
      if (!wasFire)
          Console.WriteLine(string.Format(OK, townName));      
}

Рассмотрим теперь классы receiver, обрабатывающие событие Fire. Их у нас три, по одному на каждую городскую службу. Все три класса устроены по одному образцу. Напомню: каждый такой разумно устроенный класс, кроме обработчика события, имеет конструктор, инициализирующий ссылку на объект, создающий события, методы подключения и отсоединения обработчика от события. В такой ситуации целесообразно построить вначале абстрактный класс Receiver, в котором будет предусмотрен обработчик события, но не задана его реализация, а затем для каждой службы построить класс потомок. Начнем с описания родительского класса:

public abstract class Receiver
   {        
    protected NewTown town;
    protected Random rnd = new Random();
      public Receiver(NewTown town)
         {this.town = town;}
      
    public void On()
      {
         town.Fire += new FireEventHandler(It_is_Fire);
      }
      public void Off()
      {
         town.Fire -= new FireEventHandler(It_is_Fire);
      }        
      public abstract void It_is_Fire(object sender, FireEventArgs e);      
   }//class Receiver

Каждый из классов потомков устроен одинаково - имеет конструктор и задает реализацию абстрактного метода It_is_Fire. Вот описания этих классов:

public class Police : Receiver
   {
      public Police (NewTown town): base(town){}        
      public override void It_is_Fire(object sender, FireEventArgs e)
      {
            const string OK =
                "Милиция нашла виновных!";
            const string NOK =
                "Милиция не нашла виновных! Следствие продолжается.";
            if (rnd.Next(0, 10) > 6)
                e.Result = OK;
            else e.Result = NOK;
      }
   }// class Police
   public class FireDetect : Receiver
   {
      public FireDetect (NewTown town): base(town){}        
    public override void It_is_Fire(object sender, FireEventArgs e)
    {
       const string OK =
            "Пожарные потушили пожар!";
       const string NOK =
            "Пожар продолжается! Требуется помощь.";
       if (rnd.Next(0, 10) > 4)
            e.Result = OK;
       else e.Result = NOK;
    }
   }// class FireDetect
   public class Ambulance : Receiver
   {
      public Ambulance(NewTown town): base(town){}      
    public override void It_is_Fire(object sender, FireEventArgs e)
    {
        const string OK =
            "Скорая оказала помощь!";
        const string NOK =
            "Есть пострадавшие! Требуются лекарства.";
        if (rnd.Next(0, 10) > 2)
            e.Result = OK;
        else e.Result = NOK;
    }
   }// class Ambulance

Для полноты картины необходимо показать, как выглядит класс, задающий аргументы события, который, как и положено, является потомком класса EventArgs:

/// <summary>
   /// Класс,задающий входные и выходные аргументы события
   /// </summary>
  public class FireEventArgs : EventArgs
   {      
    int building;
      int day;
      string result;
        //Доступ к входным и выходным аргументам
      public int Building
      { get{return building;} }
      public int Day
      { get{return day;}   }
      public string Result
      {
        get { return result; }
        set{result = value;} 
    }
      public FireEventArgs(int building, int day)
      {
         this.building = building; this.day = day;
      }
   }//class FireEventArgs

Входные аргументы события - build и day защищены от обработчиков события, а корректность работы с выходным аргументом гарантируется аккуратным программированием вызова обработчиков.

Для завершения проекта нам осталось определить тестирующую процедуру в классе Testing, создающую объекты и запускающую моделирование жизни города:

public void TestLifeTown()
      {
       NewTown sometown = new NewTown("Канск", 20, 100); 
         sometown.LifeOurTown();         
      }

Результаты ее работы зависят от случайных событий. Вот как выглядит один из экспериментов.

События в жизни города и три обработчика

Рис. 7.5. События в жизни города и три обработчика

Проекты

  1. Создайте проект "Жизнь города", в котором происходят разные события.
  2. Создайте проект "Жизнь факультета" с событием "День факультета".
  3. Создайте проект "Жизнь факультета", в котором происходят разные события.
  4. Создайте проект "Жизнь лошади".
  5. Создайте проект "Жизнь автомобиля".
  6. Создайте проект "Жизнь парохода".
  7. Создайте проект "Жизнь студента".
  8. Создайте проект "Работа конвейера".
  9. Создайте проект "Военные действия". Классы, задающие противников, взаимно обрабатывают события друг друга. Например, объект одного класса зажигает событие "атака", обработчик этого события в другом классе в ответ зажигает событие "контратака".
  10. Создайте проект "Быки и Медведи", где объекты класса "Быки" играют на бирже на повышение, а "Медведи" - на понижение.
  11. Исследуйте возможность создания класса, в котором одни объекты этого класса создают события, а другие объекты этого же класса обрабатывают эти события.
< Лекция 7 || Лекция 8: 12345 || Лекция 9 >
Илья Ардов
Илья Ардов

Добрый день!

Я записан на программу. Куда высылать договор и диплом?

Дарья Федотова
Дарья Федотова
Михаил Алексеев
Михаил Алексеев
Россия, Уфа, УГАТУ, 2002
Олег Корсак
Олег Корсак
Латвия, Рига