Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ооп теория

.pdf
Скачиваний:
21
Добавлен:
14.02.2015
Размер:
3.58 Mб
Скачать

protected virtual void OnFire(FireEventArgs e)

{

if(Fire != null) Fire(this, e);

}

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

public void LifeOurTown()

{

for(day = 1; day<=days; day++)

for(build =1; build <= BuildingNumber; build++)

{

if( rnd.Next(n) <=m) //загорелся дом

{

//аргументы события

FireEventArgs e = new FireEventArgs(build, day, true); OnFire(e);

if(e.Permit)

Console.WriteLine("Пожар потушен!" + " Ситуация нормализована.");

else Console.WriteLine("Пожар продолжается." + " Требуются дополнительные средства!");

}

}

}

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

public abstract class Receiver

{

private NewTown town;

public Receiver(NewTown town) {this.town = town;}

public void On()

{

town.Fire += new FireEventHandler(It_is_Fire);

431

}

public void Off()

{

town.Fire -= new FireEventHandler(It_is_Fire); town = null;

}

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)

{

Console.WriteLine("Пожар в доме {0}. День {1}-й."

+ " Милиция ищет виновных!", e.Build,e.Day); e.Permit &= true;

}

}// class Police

public class FireDetect : Receiver

{

public FireDetect (NewTown town): base(town){}

public override void It_is_Fire(object sender, FireEventArgs

e)

{

Console.WriteLine("Пожар в доме {0}. День {1}-й."+ " Пожарные тушат пожар!", e.Build,e.Day);

Random rnd = new Random(e.Build); if(rnd.Next(10) >5)

e.Permit &= false; else e.Permit &=true;

}

}// class FireDetect

public class Ambulance : Receiver

{

public Ambulance(NewTown town): base(town){} public override void It_is_Fire(object sender,

FireEventArgs e)

{

Console.WriteLine("Пожар в доме {0}. День {1}-й."+ " Скорая спасает пострадавших!",

e.Build,e.Day);

e.Permit &= true;

}

}// class Ambulance

Для каждого потомка задан конструктор, вызывающий базовый метод родителя. Каждый потомок по-своему определяет обработчика события Fire.

Обратите внимание на то, как в данном проекте решается проблема с выходным параметром события - Permit. Принята следующая стратегия:

432

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

данном примере все зависит от пожарных, которые могут вырабатывать разные решения.

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

EventArgs:

public class FireEventArgs : EventArgs

{

private int build; private int day; private bool permit; public int Build

{

get{ return(build);} ///set{ build = value;}

}

public int Day

{

get{ return(day);} ///set{ day = value;}

}

public bool Permit

{

get{ return(permit);} set{ permit = value;}

}

public FireEventArgs(int build, int day, bool permit)

{

this.build = build; this.day = day; this.permit = permit;

}

}//class FireEventArgs

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

433

Для завершения проекта нам осталось определить тестирующую процедуру в

классе Testing, создающую объекты и запускающую моделирование жизни

города:

public void TestLifeTown()

{

NewTown sometown = new NewTown(100,100); sometown.LifeOurTown();

}

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

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

434

Тема 22. УНИВЕРСАЛЬНОСТЬ. КЛАССЫ С РОДОВЫМИ ПАРАМЕТРАМИ.

СОДЕРЖАНИЕ ЛЕКЦИИ:

Наследование и универсальность

o Синтаксис универсального класса o Класс с универсальными методами

o Два основных механизма объектной технологии

oСтек. От абстрактного, универсального класса к конкретным версиям

Ограниченная универсальность o Синтаксис ограничений

o Список с возможностью поиска элементов по ключу o Как справиться с арифметикой

o Родовое порождение класса. Предложение using o Универсальность и специальные случаи классов o Универсальные структуры

o Универсальные интерфейсы o Универсальные делегаты

Framework .Net и универсальность

435

Наследование и универсальность

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

public void Swap(ref T x1, ref T x2)

{

T temp;

temp = x1; x1 = x2; x2 = temp;

}

Если тип T - это вполне определенный тип, например int, string или Person, то никаких проблем не существует, все совершенно прозрачно. Но как быть,

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

В типизированных языках, не обладающих механизмом универсализации,

выхода практически нет - приходится писать многочисленные копии Swap.

Для достижения универсальности процедуры Swap следует рассматривать тип T как ее параметр, такой же, как и сами аргументы x1 и x2. Суть универсальности в том, чтобы в момент вызова процедуры передавать ей не только фактические аргументы, но и их фактический тип.

436

Под универсальностью (genericity) понимается способность класса объявлять используемые им типы как параметры. Класс с параметрами, задающими типы, называется универсальным классом (generic class). Терминология не устоялась и синонимами термина "универсальный класс" являются термины:

родовой класс, параметризованный класс, класс с родовыми параметрами. В

языке С++ универсальные классы называются шаблонами (template).

Синтаксис универсального класса

Объявить класс C# универсальным просто: для этого достаточно указать в объявлении класса, какие из используемых им типов являются параметрами.

Список типовых параметров класса, заключенный в угловые скобки,

добавляется к имени класса:

class MyClass<T1, ... Tn> {...}

Как и всякие формальные параметры, Ti являются именами

(идентификаторами). В теле класса эти имена могут задавать типы некоторых полей класса, типы аргументов и возвращаемых значений методов класса. В

некоторый момент (об этом скажем чуть позже) формальные имена типов будут заменены фактическими параметрами, представляющими уже конкретные типы - имена встроенных классов, классов библиотеки FCL,

классов, определенных пользователем.

В C# универсальными могут быть как классы, так и все их частные случаи -

интерфейсы, структуры, делегаты, события.

Класс с универсальными методами

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

437

Давайте начнем рассмотрение универсальности с этого частного случая. Вот как выглядит класс, содержащий универсальный метод swap:

class Change

{

static public void Swap<T>(ref T x1, ref T x2)

{

T temp;

temp = x1; x1 = x2; x2 = temp;

}

}

Как видите, сам класс в данном случае не имеет родовых параметров, но зато универсальным является статический метод класса swap, имеющий родовой параметр типа T. Этому типу принадлежат аргументы метода и локальная переменная temp. Всякий раз при вызове метода ему, наряду с фактическими аргументами, будет передаваться и фактический тип, заменяющий тип T в

описании метода. О некоторых деталях технологии подстановки и выполнения метода поговорим в конце лекции, сейчас же лишь отмечу, что реализация вызова универсального метода в C# не приводит к существенным накладным расходам.

Рассмотрим тестирующую процедуру из традиционного для наших примеров класса Testing, в которой интенсивно используется вызов метода swap для различных типов переменных:

public void TestSwap()

{

int x1 = 5, x2 = 7;

Console.WriteLine("до обмена: x1={0}, x2={1}",x1, x2); Change.Swap<int>(ref x1, ref x2);

Console.WriteLine("после обмена: x1={0}, x2={1}", x1, x2); string s1 = "Савл", s2 = "Павел";

Console.WriteLine("до обмена: s1={0}, s2={1}", s1, s2); Change.Swap<string>(ref s1, ref s2); Console.WriteLine("после обмена: s1={0}, s2={1}", s1, s2);

438

Person pers1 = new Person("Савлов", 25, 1500); Person pers2 = new Person("Павлов", 35, 2100); Console.WriteLine("до обмена: "); pers1.PrintPerson(); pers2.PrintPerson(); Change.Swap<Person>(ref pers1, ref pers2); Console.WriteLine("после обмена:"); pers1.PrintPerson(); pers2.PrintPerson();

}

Обратите внимание на строки, осуществляющие вызов метода:

Change.Swap<int>(ref x1, ref x2);

Change.Swap<string>(ref s1, ref s2);

Change.Swap<Person>(ref pers1, ref pers2);

В момент вызова метода передаются фактические аргументы и фактические типы. В данном примере в качестве фактических типов использовались встроенные типы int и string и тип Person, определенный пользователем.

Общая ситуация такова: если в классе объявлен универсальный метод со списком параметров M<T1, ...Tn> (...), то метод вызывается следующим образом: M<TYPE1, ... TYPEn>(...), где TYPEi - это конкретные типы.

Еще раз напомню, что все эти примеры построены в Whidbey, и вот как выглядят внешний вид среды разработки и окно с результаты работы этой процедуры.

439

Рис. 22.1. Результаты работы универсальной процедуры swap

В этом примере использовался класс Person, и поскольку он появится и в следующих примерах, то приведу его текст:

class Person

{

public Person(string name, int age, double salary)

{

this.name = name; this.age = age; this.salary = salary;

}

public string name; public int age; public double salary;

public void PrintPerson()

{

Console.WriteLine("name= {0}, age = {1}, salary ={2}", name, age, salary);

}

}

Два основных механизма объектной технологии

Наследование и универсальность являются двумя основными механизмами,

обеспечивающими мощность объектной технологии разработки.

Наследование позволяет специализировать операции класса, уточнить, как должны выполняться операции. Универсализация позволяет специализировать данные, уточнить, над какими данными выполняются операции.

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

задаваемый родовым параметром, обязан быть наследником некоторого класса и/или ряда интерфейсов. С другой стороны, когда формальный тип T

заменяется фактическим типом TFact, то там, где разрешено появляться объектам типа TFact, разрешены и объекты, принадлежащие классам-

потомкам TFact.

Эти механизмы в совокупности обеспечивают бесшовный процесс разработки программных систем, начиная с этапов спецификации и

440