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

ооп теория

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

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

public struct Point<T>

{

T x, y;//координаты точки, тип которых задан параметром // другие свойства и методы структуры

}

УНИВЕРСАЛЬНЫЕ ИНТЕРФЕЙСЫ

Интерфейсы чаще всего следует делать универсальными, предоставляя большую гибкость для позднейших этапов создания системы. Возможно, вы заметили применение в наших примерах универсальных интерфейсов библиотеки FCL - IComparable<T> и других. Введение универсальности, в

первую очередь, сказалось на библиотеке FCL - внутренних классов,

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

то потеряли бы в эффективности, поскольку сравнение объектов потребовало бы создание временных объектов типа object, выполнения операций boxing и unboxing.

УНИВЕРСАЛЬНЫЕ ДЕЛЕГАТЫ

Делегаты также могут иметь родовые параметры. Чаще встречается ситуация, когда делегат объявляется в универсальном классе и использует в своем объявлении параметры универсального класса. Давайте рассмотрим ситуацию с делегатами более подробно. Вот объявление универсального класса, не очень удачно названного Delegate, в котором объявляется функциональный тип - delegate:

class Delegate<T>

{

461

public delegate T Del(T a, T b);

}

Как видите, тип аргументов и возвращаемого значения в сигнатуре функционального типа определяется классом Delegate.

Добавим в класс функцию высшего порядка FunAr, одним из аргументов которой будет функция типа Del, заданного делегатом. Эта функция будет применяться к элементам массива, передаваемого также функции FunAr.

Приведу описание:

public T FunAr(T[] arr, T a0, Del f)

{

T temp = a0;

for(int i =0; i<arr.Length; i++)

{

temp = f(temp, arr[i]);

}

return (temp);

}

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

Рассмотрим теперь клиентский класс Testing, в котором определен набор функций:

public int max2(int a, int b)

{ return (a > b) ? a : b; } public double min2(double a, double b)

{ return (a < b) ? a : b; } public string sum2(string a, string b)

{ return a + b; } public float prod2(float a, float b)

{ return a * b; }

Хотя все функции имеют разные типы, все они соответствуют определению класса Del - имеют два аргумента одного типа и возвращают результат того же типа. Посмотрим, как они применяются в тестирующем методе класса Testing:

public void TestFun()

{

int[] ar1 = { 3, 5, 7, 9 }; double[] ar2 = { 3.5, 5.7, 7.9 };

string[] ar3 = { "Мама ", "мыла ", "Машу ", "мылом." }; float[] ar4 = { 5f, 7f, 9f, 11f };

Delegate<int> d1 = new Delegate<int>(); Delegate<int>.Del del1;

del1= this.max2;

int max = d1.FunAr(ar1, ar1[0], del1);

462

Console.WriteLine("max= {0}", max); Delegate<double> d2 = new Delegate<double>(); Delegate<double>.Del del2;

del2 = this.min2;

double min = d2.FunAr(ar2, ar2[0], del2); Console.WriteLine("min= {0}", min); Delegate<string> d3 = new Delegate<string>(); Delegate<string>.Del del3;

del3 = this.sum2;

string sum = d3.FunAr(ar3, "", del3); Console.WriteLine("concat= {0}", sum); Delegate<float> d4 = new Delegate<float>(); Delegate<float>.Del del4;

del4 = this.prod2;

float prod = d4.FunAr(ar4, 1f, del4); Console.WriteLine("prod= {0}", prod);

}

Обратите внимание на объявление экземпляра делегата:

Delegate<int>.Del del1;

В момент объявления задается фактический тип, и сигнатура экземпляра становится конкретизированной. Теперь экземпляр можно создать и связать с конкретной функцией. В C# 2.0 это делается проще и естественнее, чем ранее, - непосредственным присваиванием:

del1= this.max2;

При выполнении этого присваивания производятся довольно сложные действия - проверяется соответствие сигнатуры функции в правой части и экземпляра делегата, в случае успеха создается новый экземпляр делегата,

который и связывается с функцией.

Покажем, что и сам функциональный тип-делегат можно объявлять с родовыми параметрами. Вот пример такого объявления:

public delegate T FunTwoArg<T>(T a, T b);

Добавим в наш тестовый пример код, демонстрирующий работу с этим делегатом:

FunTwoArg<int> mydel;

mydel = max2;

max = mydel(17, 21); Console.WriteLine("max= {0}", max);

Вот как выглядят результаты работы тестового примера:

463

Рис. 22.7. Результаты работы с универсальными делегатами

Универсальные делегаты с успехом используются при определении событий.

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

public void delegate EventHandler<T> (object sender, T args) where T:EventArgs

Этот делегат может применяться и для событий с собственными аргументами, поскольку вместо параметра T может быть подставлен конкретный тип - потомок класса EventArgs, дополненный нужными аргументами.

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

Универсальность принадлежит к основным механизмам языка. Ее введение в

язык C# не могло не сказаться на всех его основных свойствах. Как уже говорилось, классы и все частные случаи стали обладать этим свойством.

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

Решение этих задач потребовало введения универсальности не только в язык

C#, но и поддержки на уровне каркаса Framework .Net и языка IL,

включающем теперь параметризованные типы. Универсальный класс C# не

464

является шаблоном, на основе которого строится конкретизированный класс,

компилируемый далее в класс (тип) IL. Компилятору языка C# нет необходимости создавать классы для каждой конкретизации типов универсального класса. Вместо этого происходит компиляция универсального класса C# в параметризованный тип IL. Когда же CLR

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

сопровождающих объекты.

При этом дублирования кода не происходит и на уровне JIT-компиляторов,

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

Естественно, что универсальность потребовала введения в библиотеку FCL

соответствующих классов, интерфейсов, делегатов и методов классов,

обладающих этим свойством.

Так, например, в класс System.Array добавлен ряд универсальных статических методов. Вот один из них:

public static int BinarySearch<T>(T[] array, T value);

В таблице 22.1 показаны некоторые универсальные классы и интерфейсы библиотеки FCL 2.0 из пространства имен System.Collections.Generic и их аналоги из пространства System.Collections.

Таблица 22.1. Соответствие между универсальными классами и их обычными двойниками

 

Универсальны

 

Обычн

 

Универсальны

 

 

Обычный

 

 

 

 

 

 

 

 

й класс

 

ый класс

 

й интерфейс

 

 

интерфейс

 

 

Comparer<T>

 

Compar

 

ICollection<T>

 

 

ICollection

 

 

 

 

 

 

 

 

465

 

 

 

er

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Dictionary<K,T>

 

 

HashTa

 

IComparable<T>

 

IComparab

 

 

 

 

 

 

 

 

 

 

ble

 

 

 

 

le

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

LinkedList<T>

 

 

----

 

IDictionary<K,T

 

IDictionary

 

 

 

 

 

 

 

>

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

List<T>

 

 

ArrayLi

 

IEnumerable<T>

 

IEnumerab

 

 

 

 

st

 

 

 

 

le

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Queue<T>

 

 

Queue

 

IEnumerator<T>

 

IEnumerat

 

 

 

 

 

 

 

 

 

or

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

SortedDictionary

 

 

SortedL

 

IList<T>

 

IList

 

 

<K,T>

 

ist

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Stack<T>

 

 

Stack

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

466

Тема 23. ОТЛАДКА И ОБРАБОТКА ИСКЛЮЧИТЕЛЬНЫХ СИТУАЦИЙ.

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

Корректность и устойчивость программных систем o Жизненный цикл программной системы

oТри закона программотехники

Первый закон (закон для разработчика)

Второй закон (закон для пользователя)

Третий закон (закон чечако)

Отладка

o Создание надежного кода o Искусство отладки

o Отладочная печать и условная компиляция o Классы Debug и Trace

o Метод Флойда и утверждения Assert o Классы StackTrace и BooleanSwitch

o Отладка и инструментальная среда Visual Studio .Net

Обработка исключительных ситуаций

o Обработка исключений в языках C/C++ o Схема обработки исключений в C#

o Выбрасывание исключений. Создание объектов Exception o Захват исключения

o Параллельная работа обработчиков исключений o Блок finally

o Схема Бертрана обработки исключительных ситуаций o Класс Exception

467

КОРРЕКТНОСТЬ И УСТОЙЧИВОСТЬ ПРОГРАММНЫХ СИСТЕМ

Корректность и устойчивость - два основных качества программной системы,

без которых все остальные ее достоинства не имеют особого смысла.

Понятие корректности программной системы имеет смысл только тогда,

когда задана ее спецификация. В зависимости от того, как формализуется спецификация, уточняется понятие корректности.

В лекции 9 введено строгое понятие корректности метода по отношению к его спецификациям, заданным в виде предусловия и постусловия метода.

Корректность - это способность программной системы работать в строгом соответствии со своей спецификацией. Отладка - процесс, направленный на достижение корректности.

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

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

Почему так трудно создавать корректные и устойчивые программные системы? Все дело в сложности разрабатываемых систем. Когда в 60-х годах прошлого века фирмой IBM создавалась операционная система OS-360, то на ее создание потребовалось 5000 человеко-лет, и проект по сложности сравнивался с проектом высадки первого человека на Луну. Сложность нынешних сетевых операционных систем, систем управления хранилищами данных, прикладных систем программирования на порядки превосходит сложность OS-360, так что, несмотря на прогресс, достигнутый в области

468

технологии программирования, проблемы, стоящие перед разработчиками,

не стали проще.

Жизненный цикл программной системы

Под "жизненным циклом" понимается период от замысла программного продукта до его "кончины". Обычно рассматриваются следующие фазы этого процесса:

Проектирование <-> Разработка <-> Развертывание и Сопровождение

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

Вот некоторые типовые правила, характерные для процесса разработки ПО:

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

Помните о тех, для кого разрабатывается программный продукт. Идите

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

469

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

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

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

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

Три закона программотехники

Первый закон (закон для разработчика)

Корректность системы - недостижима. Каждая последняя найденная

ошибка является предпоследней.

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

которых система работает не в точном соответствии со своей спецификацией,

470