| Оплата и обучение |
Классы с событиями
Проект "Город и его службы"
Завершить лекцию хочется проектом, в котором собраны все рассматриваемые аспекты работы с событиями. Вернемся к моделированию жизни города, происходящих в нем событий, реакции на них городских служб. Наша главная цель в данном проекте еще раз показать, как возникающее событие, в данном случае - пожар в одном из домов города, обрабатывается по-разному городскими службами - пожарными, милицией, скорой помощью. В нашей модели сообщение о событии получает не одна, как ранее, а несколько служб. Событие имеет как входные, так и выходные аргументы, так что городу необходимо будет анализировать результаты работы каждой службы. Стоит обратить внимание и на организацию взаимодействия между классами, задающего город и его службы. Конечно, все упрощено, в реальном городе событиями являются не только пожары и преступления, но и более приятные ситуации: день города, открытие фестивалей и выставок, строительство новых театров и институтов.
Начнем с описания делегата, задающего событие "пожар" и построенного по всем правилам.
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();
}Результаты ее работы зависят от случайных событий. Вот как выглядит один из экспериментов.
Проекты
- Создайте проект "Жизнь города", в котором происходят разные события.
- Создайте проект "Жизнь факультета" с событием "День факультета".
- Создайте проект "Жизнь факультета", в котором происходят разные события.
- Создайте проект "Жизнь лошади".
- Создайте проект "Жизнь автомобиля".
- Создайте проект "Жизнь парохода".
- Создайте проект "Жизнь студента".
- Создайте проект "Работа конвейера".
- Создайте проект "Военные действия". Классы, задающие противников, взаимно обрабатывают события друг друга. Например, объект одного класса зажигает событие "атака", обработчик этого события в другом классе в ответ зажигает событие "контратака".
- Создайте проект "Быки и Медведи", где объекты класса "Быки" играют на бирже на повышение, а "Медведи" - на понижение.
- Исследуйте возможность создания класса, в котором одни объекты этого класса создают события, а другие объекты этого же класса обрабатывают эти события.
