МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE Тема 9. Объектно-ориентированное программирование Цель Рассмотреть принципы программирования на основе событий. Задачи 1. Познакомиться с особенностями модели программирования на основе событий (event-driven programming). 2. На примере событий стандартных классов рассмотреть принципы написания обработчиков событий. 3. Научиться управлять событиями собственных классов. 4. Рассмотреть технологию обработки исключений в среде .NET Framework. 5. Познакомиться с технологией конструирования программ. Оглавление Тема 9. Объектно-ориентированное программирование ................ 1 Понятие события .................................................................................... 1 Обработка событий в программе ........................................................ 3 Разработка событий собственных классов ....................................... 4 Обработка исключений в среде .NET Framework .............................. 8 Конструирование программ: создание библиотеки классов ........ 10 Выводы .................................................................................................. 10 Вопросы для самопроверки ............................................................... 11 Литература ............................................................................................. 11 Понятие события Одной из наиболее важных особенностей объектно-ориентированного программирования является возможность использования модели программирования на основе событий (event-driven programming). Этот способ не нов, сама среда Windows является средой, управляемой событиями. Это означает, что в Windows ничего не происходит само по себе, а только в ответ на нечто (событие), обнаруженное Windows. Примером таких событий может быть щелчок мыши на ярлыке на рабочем столе, нажатие клавиши на клавиатуре или открытие меню Пуск. На первый взгляд, все понятно, но технология работы с событиями вовсе не так проста, как кажется. Даже при определении понятия МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE «событие» возникают разногласия. Что такое «событие»? Трудно сказать. Под событием принято понимать действие, но под событием часто понимают и сообщение о нем. Твердо можно сказать только одно, что при инициировании события вызывается соответствующий метод вашего объекта. Чем непосредственный вызов метода объекта отличается от вызова того же метода по событию? С сугубо концептуальной точки зрения – это всего лишь вопрос восприятия. Рассмотрим два объекта. Клиентский объект является частью вашей программы и создает серверный объект для выполнения некоторой задачи (рис. 9.1). Серверный объект при необходимости может передать клиенту некоторую информацию. Концептуальное различие между вызовом метода и метода по событию связано с направлением вызова. Вызывая метод из своего приложения, вы сами определяете, когда это происходит, то есть вызов входит в нормальную последовательность выполнения программы. События можно рассматривать как вызовы методов, не входящие в нормальную последовательность выполнения, на которые ваше приложение должно определенным образом отреагировать. Клиент Непосредственный вызов методов Сервер Вызов методов через события Метод события Рис. 9.1. Простейшая схема обработки событий Программирование на основе событий коренным образом изменило характер модели программирования. Традиционные языки программирования выполняют программу последовательно от строки к строке. Даже если используется функции и процедуры, это не слишком меняет порядок выполнения: одна процедура вызывает другую, которая вызывает третью и так далее. Другими словами, здесь присутствует упорядоченная последовательность действий. Концепция программирования на основе событий меняет все. Последовательный способ выполнения не подходит для события. Так, среда Windows не занимается последовательным выполнением действий. Если пользователь щелкает мышью в меню, он требует немедленного появления меню, никто не хочет сидеть и ждать, пока Windows закончит выполнять то, чем она была занята до этого. Прежде, чем мы с вами начнем создавать события в своих классах, рассмотрим, как эти события устроены в стандартных классах и каким образом следует программировать обработку событий. МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE Обработка событий в программе Все события, с которыми мы сталкивались до сих пор, относились к стандартным классам либо Forms, либо Controls (элементы управления, расположенные на форме). Наиболее важными событиями стандартных классов Forms и Controls являются: события Load, Paint, Closed и Closing, происходящие при загрузке, перерисовке, закрытии форм, события KeyDown, KeyUp и KeyPress, происходящие при нажатии клавиш клавиатуры, события MouseDown, MouseUp и MouseMove, происходящие при перемещении указателя мыши. Сигнатура метода, выполняющего обработку соответствующего события, имеет, например, следующий вид: Private Sub Form2_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseDown, где sender – объект, который является источником (отправителем) события, объект e – это объект стандартного класса EventArgs (в данном случае System.Windows. Forms.MouseEventArgs), который содержит сведения о произошедшем событии (аргументы события), Handles MyBase.MouseDown – директива компилятору: какой из объектов инициирует какое событие. Объект e имеет свойства, которым присваиваются определенные значения при выполнении события, например, при нажатии кнопки мыши в объект e передаются координаты указателя мыши и состояние кнопок (какая из них нажата/отпущена). Значения свойств объекта e можно считывать – таким образом, и происходит программирования реакции на произошедшее событие. Например, напишем обработчик события нажатия кнопки мыши в форме. Если событие происходит, то объект e получает информацию о том, какая из кнопок нажата и координаты указателя мыши в форме: свойства Button и X, Y соответственно. Значения этих свойств можно прочитать: Private Sub Form2_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseDown If e.Button = MouseButtons.Left Then MsgBox(e.X, e.Y) End If End Sub Другой пример стандартного события. Событие KeyUp происходит, когда пользователь отпускает любую нажатую клавишу на клавиатуре. В процедуру обработчик этого события передается параметр e, являющийся объектом теперь другого стандартного класса – System. Windows. Forms. KeyEventArgs и имеющий другие свойства: Alt, Control, Handler, KeyCode и Shift. Эти свойства позволяют узнать код отжатой клавиши и состояние специальных клавиш Control, Alt, Shift. МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE Private Sub Form2_KeyUp(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyUp If e.KeyCode = Keys.Insert Then MsgBox("Была нажата клавиша Insert") End Sub Подобные обработчики событий используются во всех событийных приложениях, то есть приложениях, поддерживающих модель программирования на основе событий (event-driven programming). Кратко рассмотрим архитектуру событий .NET Framework. Объект, который генерирует событие, известен как источник события. Например, объект Button является источником событий, потому что он генерирует такие события, как Click или MouseDown. Источник событий объявляет события, которые он может генерировать; в VB.NET мы используем ключевое слово Event для объявления события в определении класса. Объект, который определяет методыобработчики события, известен как получатель события. Получатель события должен предоставить метод-обработчик события с сигнатурой, соответствующей тому событию, для которого он регистрируется. Источник события генерирует события при выполнении своих методов. Например, объекты Button генерируют событие Click, когда пользователь щелкает мышью на кнопке (экране). Разработка событий собственных классов Теперь рассмотрим, каким образом можно добавить события в собственные классы. В соответствии с принципом инкапсуляции события являются членами класса и должны быть объявлены в его определении. Для объявления событий в VB.NET мы должны объявить члены с ключевым словом Event в классе-источнике событий (или серверном классе). Мы должны указать два вида информации для каждого события: Имя события. Например, класс Person объявляет событие с именем InfoUpdate (была добавлена новая информация о сотруднике). Мы должны объявить члены Event для этого события нашего класса, поскольку оно способно заинтересовать другие объекты в клиентском приложении. Сигнатуру (список параметров) события. Когда объектисточник события генерирует событие, он предоставит значения для этих параметров. Эти значения будут переданы в метод-обработчик события в объекте-получателе события. Это подразумевает, что методы-обработчики события должны иметь такую же сигнатуру, как и событие. Имеется два способа объявления событий в классе-источнике события: указать сигнатуру события явно, как часть объявления МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE события и указать сигнатуру события неявно, используя делегата. Мы с вами ограничимся первым способом, так как понятие «делегат» мы не рассматриваем. Можно это сделать самостоятельно, пользуясь литературой, список которой приведен в конце темы. Итак, рассмотрим явное определение сигнатуры события в собственном (в данном случае серверном) классе. Можно определять одно событие с явным заданием сигнатуры, а можно определять несколько событий, которые имеют одинаковую сигнатуру. Следующий пример показывает, как определить одно событие с использованием явного задания его сигнатуры. Пример использует класс Person и определяет событие с именем InfoUpdate. Public Class Person Public Event InfoUpdate (ByVal sender As Object, ByVal e As MyEventArgs) Обратите внимание на следующие моменты в этом примере. В VB.NET мы используем ключевое слово Event для объявления событий в классе. События – это члены класса, и, следовательно, имеют модификаторы доступа. По умолчанию они объявляются как Public, потому что природа событий такова, что они публикуют информацию для других объектов приложения. Однако если это необходимо, то существует возможность ограничить область видимости событий как Private (только для методов нашего класса), Friend (только для методов сборки), Protected (только для методов нашего класса и методов классов, наследников нашего класса). Событие InfoUpdate имеет два параметра. Первый из них дает ссылку на объект, сгенерировавший событие, а второй (объект MyEventArgs) инкапсулирует в себе информацию о событии. Для простоты в данном случае этот объект будет содержать только одно скрытое поле и одно свойство доступа к нему. Public Class myEventArgs Inherits System.EventArgs Private _m As String Sub New(ByVal value As String) MyBase.New() Me._m = value End Sub Public Property Messages() As String Get Return _m End Get Set(ByVal Value As String) _m = Value End Set МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE End Property End Class Мы должны будем предоставлять эти параметры в процессе генерации события. Затем параметры будут переданы в методобработчик события так, что объекты-получатели события получат некоторое представление о том, о каком сотруднике идет речь и удалось ли информацию о нем добавить. События не могут иметь возвращаемого значения. Они представляют собой однонаправленный поток информации от объекта-источника события к объекту-получателю события. Указание возвращаемого значения для события является ошибкой. Теперь необходимо описать метод, при выполнении которого будет происходить событие. Процесс генерации события описывается с ключевым словом RaiseEvent. Ниже показано как может выглядеть метод класса, в рамках которого генерируется событие. Например, происходит проверка на допустимость символов в имени Person. Public Sub SaveInfo() Dim c As Char Dim e As New myEventArgs("Имя ") For Each c In Me.Name If Char.IsDigit(c) Then e.Messages &= "недопустимо" RaiseEvent InfoUpdate(Me.Name, e) Exit Sub End If Next e.Messages &= "допустимо" RaiseEvent InfoUpdate(Me.Name, e) End Sub Обратите внимание на следующие моменты: оператор RaiseEvent требует имя события, которое мы хотим генерировать, за которым должен следовать список значений параметров, заключенных в скобки, эти значения будут переданы в метод-обработчик этого события. Если событие не принимает никаких параметров, мы должны опустить скобки. Мы рассмотрели, как объявить событие, теперь рассмотрим, как объекты получатели события могут «подписаться» на это событие. Имеется два способа подписаться на событие: определить обработчик события статически, используя ключевые слова WithEvents и Handles, определить обработчик события динамически, используя ключевые слова AddHandler и RemoveHandler. Рассмотрим статическое определение событий в клиентском классе. МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE Для того чтобы клиент получил сообщение о событии сервера, в определении объекта серверного класса необходимо указать ключевое слово WithEvent, при этом область видимости данного объекта должна быть ограничена классом. После объявления объекта серверного класса для обработки события можно использовать любой метод клиентского класса, список аргументов, которого совпадает со списком, передаваемым сообщением о событии. Ниже представлен класс формы, который обрабатывает событие серверного класса. Public Class Form2 Inherits System.Windows.Forms.Form Dim WithEvents oPerson As Person Dim dPerson As Person Private Sub Button1_Click_1(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click oPerson = New Person("Федоркин") dPerson = New Person("1Перовский") oPerson.SaveInfo() dPerson.SaveInfo() End Sub Private Sub oPerson_InfoUpdate(ByVal sender As Object, ByVal e As myEventArgs) Handles oPerson.InfoUpdate MsgBox(e.Messages) End Sub End Class Имеется несколько ограничений на использование ключевого слова WithEvents. Мы можем использовать WithEvents для типов, которые генерируют события. Это следует учитывать; незачем обрабатывать события объекта, который никогда не генерирует событий. Мы можем использовать WithEvents только для обработки событий типов Class и Interface. Мы не можем использовать WithEvents для обработки событий типа Structure. Мы можем использовать WithEvents только для обработки событий экземпляра. Мы не можем использовать WithEvents для обработки Shared-событий, потому что механизм WithEvents требует наличия экземпляра объекта. Любой метод может принимать любое число событий от любого числа объектов – при условии, что сигнатура метода соответствует сигнатуре делегата события. Рассмотрим пример обработки нескольких событий. В VB.NET массивы элементов управления не поддерживаются, то есть невозможно, например, по индексу, определить – какой именно элемент из группы был выбран пользователем. В приведенном ниже фрагменте три разных переключателя ассоциируются с одним событием. Private Sub Options_CheckedChanged (ByVal sender as System.Object, МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE ByVal e As System.EvebtArgs) Handles radioButton3.CheckedChanged, radioButton2.CheckedChanged, radioButton1.CheckedChanged Dim rb As RadioButton Rb=CType(sender, RadioButton) Console.WriteLine(rb.Name & “ “ & rb.Text) End Sub Метод Options_CheckedChanged связывает с событием CheckedChanged три разных переключателя, для чего все события перечисляются после ключевого слова Handles. Параметр sender указывает, каким переключателем было инициировано событие. Поскольку массивы элементов в VB.NET не поддерживаются, вам не удастся идентифицировать источник события по значению свойства Index. В зависимости от типа приложения для идентификации источника можно воспользоваться любым другим подходящим свойством, например свойствами Name, Text или позицией элемента. Если вы твердо уверены, что все элементы относятся к одному типу, параметр sender можно объявить с типом соответствующего элемента и обращаться к свойству напрямую, как показано выше для объекта rb, объявленного с типом RadioButton (кнопка-переключатель). Обработка исключений в среде .NET Framework При создании программ большое значение имеет обработка исключений (ошибок) – непредвиденных программистом ситуаций, которые могут возникать при выполнении приложения. Например, это может быть попытка сохранить информацию в базе данных на сервере при отсутствии соединения, выход значения индекса массива за допустимые пределы и так далее. В среде .NET Framework используется структурированный механизм обработки исключений, позволяющий делать это без аварийного завершения программы. К основным преимуществам данного механизма можно отнести использование единых средств поддержки обработки исключений во всех языках среды .NET, возможность создания защищенных блоков кода и фильтрации исключений, гарантирующих корректное завершение задач даже при возникновении фатальных ошибок. Среда .NET Framework предоставляет также большое количество встроенных классов исключений, используемых для обработки стандартных ошибок. Например, класс FileNotFoundException содержит информацию об имени файла, сообщение об ошибке и источник исключения, то есть код, содержащий операторы открытия несуществующего файла. Кроме того, среда .NET Framework позволяет программисту создавать собственные классы. Которые используются для обработки исключений, возникающих во время работы приложения. МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE Для обработки исключений используется конструкция Try Catch Finally End Try. Обобщенный синтаксис структурной обработки ошибок выглядит следующим образом: Try инструкции блока Try Catch ИмяОбъекта1 As ТипИсключения1 (ошибки1) инструкции блока Catch 1, выполняемые при возникновении ошибки Catch ИмяОбъекта1 As ТипИсключения2 (другой тип ошибки) инструкции блока Catch 2, выполняемые при возникновении ошибки Catch ИмяОбъектаN As ТипИсключенияN (другой тип ошибки) инструкции блока Catch N, выполняемые при возникновении ошибки Finally инструкции блока Finally, выполняемые перед выходом из блока Try End Try Здесь необязательными являются все блоки Catch, кроме первого, а также блок Finally. Блок Try (от ключевого слова Try до первого Catсhблока) определяет фрагмент программы, в котором производится отслеживание ошибок. Если в этом фрагменте будет генерироваться исключение, запустится процедура поиска подходящего для него обработчика среди имеющихся Catсh-блоков. Каждый Catch-блок представляет собой обработчик некоторого типа исключения. После того, как исключение будет обработано, то есть, будут выполнены все инструкции соответствующего блока Catch, управление передается на первую строку блока Finally, а при его отсутствии – на строку, следующую за инструкцией End Try. Инструкции блока Finally выполняются всегда – независимо от того, возникали исключительные ситуации в блоке Try или нет. Блок Finally обычно используется для освобождения системных ресурсов, закрытия открытых файлов и так далее. Внутри каждого блока могут находиться другие блоки, в том числе и вложенные блоки Try End Try. Различные типы ошибок представляются объектами исключений (exceptions), производными от класса System.Exception. Блок Сatch также может содержать секцию When с указанием дополнительного условия. Блок Catch выполняется только при возникновении ошибки определенного типа и при истинности условия, заданного в секции When. Но важнейшие изменения в области обработки исключений в .NET связаны не с синтаксисом структуры Try Catch (при всей ее важности) и даже не с тем, что все ошибки времени выполнения программы инициируют исключения, перехватываемые механизмом структурной обработки ошибок. Самое главное заключается в том, что все ошибки, возникающие во всех методах и свойствах объектов .NET Framework, МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE также инициируют исключения, структурной обработки ошибок. перехватываемые механизмом Конструирование программ: создание библиотеки классов Библиотека базовых классов .NET Framework (Base Class Library Framework) находится на самом верхнем уровне иерархии CLR. Она содержит классы, обеспечивающие поддержку ссылочных и обычных типов, инкапсулирующих доступ к функциям системы. Создавая собственный класс, вы разрабатываете его как наследника библиотечного класса System.Object, находящегося на самом верхнем уровне иерархии библиотеки классов. Для того чтобы ваш класс мог быть использован не только в том проекте, где вы его описали в модуле Class, но и в других проектах, необходимо создавать описание класса в отдельном проекте, затем откомпилировать его и организовать на него ссылку в других проектах. На основе разработанных классов можно создавать библиотечные файлы (DLL-файлы, Dynamic Link Library) с повторно используемым кодом. Рассмотрим технологию создания такого библиотечного файла. При создании нового проекта выберите шаблон Class Library, введите имя проекта, например, MyLib.vb. Введите описание класса, не забудьте описать NameSpace для вашего класса. Выберите команду Build – Build MyLib, чтобы откомпилировать созданную библиотеку (в нашем случае результатом компиляции будет файл MyLib.dll). Для включения библиотеки в проект выберите команду Project – Add Reference. Найдите через Browse искомую ссылку на файл с расширением dll. Не забудьте при обращении к экземпляру класса указать название библиотеки и пространство имен в этой библиотеке. Выводы Все создаваемые в среде .NET Framework приложения являются объектно-ориентированными, и управление ими осуществляется путем обработки событий. В VB.NET события являются членами класса и должны быть объявлены в его определении. Источник событий объявляет события, которые он может генерировать, используем для этого ключевое слово Event для объявления события в определении класса. Получатель события должен предоставить метод-обработчик события с сигнатурой, соответствующей тому событию, для которого он регистрируется. В термине «структурная обработка ошибок» главным словом является слово «структурная». В языках программирования, имеющих блочную структуру, программист определяет блоки программного кода, выполняемые как единое целое. Структурная обработка ошибок МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE расширяет этот принцип в области обработки ошибок. Но даже организованная структурно, обработка ошибок могла бы не иметь большой практической пользы, если бы не поддерживалась библиотеками и объектами той среды, в которой вы программируете. Все ошибки, возникающие во всех методах и свойствах объектов .NET Framework, инициируют исключения, перехватываемые механизмом структурной обработки ошибок – это главное достоинство механизма Try Catch, реализованного в .NET Framework. В библиотеке базовых типов .NET Framework имеется тип System.Object, который является родительским типом для всех пользовательских типов, разрабатываемых пользователем. Пользовательские типы наследуют методы и свойства родительского класса, в дальнейшем переопределяя их под свои требования. Для того, чтобы созданная вами библиотека стала общим достоянием и могла быть использована многократно, описание классов следует выполнять в специальном проекте (шаблон класса), выполнить компиляцию проекта, а затем делать ссылки (Reference) на файл с библиотекой из текущих проектов. 1. 2. 3. 4. 5. 6. 7. Вопросы для самопроверки Что такое «событие»? Под событием принято понимать действие или сообщение о нем? Как мы объявляем (publish) события в собственном классе? Как мы регистрируем (subscribe) получателя события? Как мы генерируем (raise) события? Что представляет собой архитектура событий в .NET Framework? Происходит ли автоматическое добавление созданной вами библиотека классов в библиотеку базовых классов .NET Framework? При возникновении ошибки (исключения) программа проверяет все блоки Catch и двигается вниз до тех пор, пока не будет найден подходящий блок. Почему принято обработчики конкретных ошибок размещать в первых блоках Catch, а обработчики общих ошибок – в конце списка? Литература 1. Чакраборти А., Кранти Ю., Сандху Р.Дж. Microsoft .NET Framework: разработка профессиональных проектов: Пер. с англ. — СПб.: БХВ-Петербург, 2005. – 896 с.: ил. 2. Ольсен Э., Эллисон д., Спир Дж. Visual Basic .NET. Разработка классов. Справочник/Практ. Пособ./Пер. с англ. — М. Издательство «СП ЭКОМ», 2006. – 416 с. ил. МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 3. Троелсен Э. C# и платформа .NET. Библиотека программиста — СПб.: Питер, 2006 — 796 с.: ил. 4. Visual Basic.NET: учебный курс/ В.Долженков, М.Мозговой. — СПб.: Питер, 2005. – 465 с,: ил.