МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 1 Практическая работа по теме 8. Модели решения функциональных и вычислительных задач Конструкторы и деструкторы классов. Методы как интерфейс класса. Сигнатура методов: процедуры и функции. Перегрузка методов и конструкторов класса – разновидность полиморфизма. Наследование классов. Базовый класс, его конструкторы, свойства, методы. Ключевые слова MyBase, MyClass, Me. Полиморфизм. Переопределенные методы. Абстрактные классы, их назначение и устройство. Применение модификаторов доступа в базовых классах: Public, Private, Protected. Использование затенения (shadowing) методов базового класса. Практическая работа по теме 8. Модели решения функциональных и вычислительных задач ................................................................................................................................................1 Задание 1. Разработка конструкторов и деструкторов класса ..............................................1 Задание 2. Методы как интерфейс класса ...............................................................................4 Задание 3. Перегрузка методов и конструкторов класса – разновидность полиморфизма6 Задание 4. Создание класса Vector (методов класса) .............................................................9 Задание 5. Работа с простыми дробями.................................................................................12 Задание 6. Наследование конструкторов, свойств и методов базового класса .................14 Задание 7. Полиморфизм. Переопределенные методы ........................................................18 Задание 8. Практическое использование иерархии классов ...............................................20 Задание 9. Абстрактные классы .............................................................................................21 Задание 10. Абстрактные классы в стандартных библиотеках ...........................................22 Задание 11. Применение модификаторов доступа в базовых классах ...............................26 Задание 12. Затенение свойств и методов базового класса .................................................27 Задание 13. Иерархия классов «Фигуры» .............................................................................29 Задание 1. Разработка конструкторов и деструкторов класса Задача Управлять процессом создания и удаления объектов класса с помощью методов класса. Решение Разработать собственные методы класса New() позволяющие создавать или удалять объекты класса. и Dispose(), Обсуждение Поскольку создание и использование конструкторов класса было рассмотрено в теме 2 (без описания конструкторов невозможно было бы выполнить тестирование свойств класса), сейчас мы остановимся на более тонких моментах в применении конструкторов – на области видимости конструктора класса. МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 2 Это очень важный момент, так как возможность создания экземпляров класса зависит от области видимости конструкторов этого класса. Ограничивая область видимости конструкторов ключевыми словами (модификаторами) Private или Friend, можно запретить создание объектов за пределами сборки. Конструкторы класса могут быть закрытыми, то есть объявленными с ключевым словом Private и доступными только собственным методам и свойствам класса. Для чего нужен закрытый конструктор, который может вызываться только в пределах класса? Ведь экземпляры класса внутри самого класса обычно не создаются? Закрытые конструкторы полезны в тех случаях, когда объект класса должен создавать дополнительные экземпляры своего класса или если вы хотите, чтобы создание объекта класса выполнялось не обычным стандартным образом (оператором New), а другим, менее привычным образом. В качестве альтернативного способа создания экземпляра класса можно использовать Shared-методом класса. Shared-метод, как и Shared-свойство, является общим методом класса, решает общие для всего класса задачи. В отличие от Shared-свойства, которое вызывается только с именем класса, Shared-метод может вызывается с именем экземпляра класса. Ниже приведен пример описания закрытого конструктора, использование Shared-метода для создания экземпляра класса и тестирование применения такого подхода. Public Class Class1 Public name As String Private Sub New() name = "Default" End Sub Public Sub New(ByVal NewName As String) name = NewName End Sub Public Shared Function DefaultObject() As Class1 Return New Class1 End Function End Class Класс Class1 имеет два конструктора, один из которых является закрытым конструктором, так как объявлен с областью видимости Private. Это конструктор создает экземпляр класса в режиме «поумолчанию», то есть с какими-то постоянными значениями свойств. Как обратиться к закрытому конструктору? Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 3 Dim c As New Class1("First") Создать объект при помощи закрытого конструктора невозможно. К закрытому конструктору можно обратиться только при помощи открытого метода. Dim c1 = c.DefaultObject() Label1.Text = c.name Label2.Text = c1.name End Sub На рис. 1. видно, что оба экземпляра класса были успешно созданы. Рис. 1. Тестирование конструкторов класса Самостоятельно Добавьте в описание Class1 Shared-свойство для подсчета количества созданных экземпляров и протестируйте его. Пример создания и использования Shared-свойств рассмотрен в задании 5 темы 2. Теперь рассмотрим как в .NET происходит удаление объектов. Компания Microsoft встроила в CLR автоматическую систему управления памятью в виде системного класса Garbage Collector, который называют «сборщиком мусора». При создании объекта CLR выделяет для него память, а «сборщик мусора» периодически проверяет память в поисках объектов, которые больше не используются (на которые отсутствуют ссылки), и удаляет их. Другими словами, удаление ненужных объектов выполняется автоматически. Применение класса Garbage Collector, конечно же, предоставляет разработчика .NET определенные удобства, но, с другой стороны, они при этом лишаются возможности контролировать процесс удаления объектов из памяти. МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 4 Для того чтобы обойти эти проблемы, компания Microsoft рекомендует применять следующую технологию: если вам нужно управлять процессом создания объектов и удалением их из памяти, создавайте собственный метод (деструктор), который будет вызываться после последнего обращения к объекту. Код, приведенный ниже, выполняет пользовательский метод Dispose(), освобождающий ресурсы. Public Sub Dispose() Me.value = Nothing Counter -= 1 Если была объявлена переменная типа Shared End Sub Самостоятельно Добавьте в описание класса Class1 деструктор класса (метод, удаляющий объекты класса из оперативной памяти) и напишите программный код для тестирования деструктора. Задание 2. Методы как интерфейс класса Задача Разработать интерфейс класса. Решение Использовать процедуры и функции для реализации поведения объектов класса. Обсуждение Требуемое поведение объектов класса обеспечивается не только при помощи свойств, но и при помощи методов. Метод – это функция или процедура, определенная как часть реализации класса. Как и свойства, методы класса могут быть открытыми или закрытыми. Открытые методы составляют интерфейс класса, а закрытые используются для внутренних нужд класса. Воспользуемся классом Persona, разработку которого мы начинали в заданиях к теме 7. Добавим в описание класса методы Login и ToShow. Метод Login, реализованный в виде функции, позволяет получить пароль пользователя, собранный из его имени и личного идентификатора. Рассмотрим пример функции, назначающей Login для класса Persona: МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 5 Public Function Login(ByVal fullname As String) As String Return Me.FullName & Me.PersonaID.ToString End Function Другой пример методов – функция ToShow(), позволяющая выполнять обычный текстовый вывод свойства FullName с изменением регистра строки: Public Function ToShow() As String Return FullName.ToUpper End Function Самостоятельно Добавьте в описание класса Persona свойство, позволяющее хранить изображение (фото сотрудника). Самостоятельно разработайте метод, позволяющий выводить это изображение в любой элемент управления, поддерживающий графику, например, в PictureBox или непосредственно на форму. Пример использования графического вывода приведен на рис. 2. Рис. 2. Пример использования методов графического вывода Мы с вами уже знакомы с графическими свойствами объектов, но в качестве подсказки можно рассмотреть варианты написания метода вывода графики в элемент управления. В классе Persona вы добавляете описание свойства и метода. Public Class Persona Private _fullName As String Private _personaID As Integer Private _password As String Private Shared count As Integer = 0 Public foto As Image МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 6 Public Sub ShowFoto(ByVal s As String) Me.foto = Image.FromFile(s) End Sub В проект добавляете форму, состоящую, например, из текстового окна и графического окна Picturebox (как на рис. 2). К процедуре загрузки формы можно написать следующий код: Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Dim p As New Persona("Макдональд Мэтью", "ММ") TextBox1.Text = p.FullName p.ShowFoto("L:\VB.NET\big_me2.jpg") PictureBox1.Image = p.foto End Sub Имя файла, содержащего изображение, должно соответствовать вашему проекту. В этом варианте мы использовали стандартный класс Image и его Shared метод Image.FromFile, позволяющий получить графическое изображение из файла, имя которого передано в метод в качестве входного параметра. Далее, мы это изображение присваиваем нашему свойству foto, объявленному с типом image. Задание 3. Перегрузка методов и конструкторов класса – разновидность полиморфизма Задача Определить не один, а несколько методов, имеющих одно и тоже имя, но выполняющих разные задачи. Решение Использовать механизм перегрузки методов (полиморфизм). Обсуждение Перегрузка методов (один из случаев полиморфизма) в классе осуществляется путем определения нескольких методов, имеющих одинаковые имена, но различные сигнатуры. Сигнатура метода – это комбинация имени метода и списка его формальных параметров. Если вы внесете изменения в список параметров метода (типы параметров, их количество), то создадите новую сигнатуру метода, то есть другой метод, но с тем же именем. При обращении к методу вы задаете список фактических параметров. Компилятор, после анализа всех имеющихся для метода с таким МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 7 именем списков параметров, определит, какой именно метод из списка одноименных необходимо вызвать для вас. В каких случаях и для чего используют полиморфизм? При описании классов довольно часто используют перегрузку для создания нескольких вариантов конструктора класса New(), что позволяет поразному инициировать экземпляры класса. Полиморфизм используют и при программировании других методов класса, так как одинаковые по смыслу действия (имеющие отличия) удобнее именовать одинаково. В сигнатуре перегружаемого метода следует указать модификатор Overloads. Рассмотрим пример нашего класса Persona. Public Overloads Sub ShowFoto(ByVal s As String) End Sub Public Overloads Sub ShowFoto(ByVal obj As Object, ByVal s As String) End Sub Ключевое слово (модификатор) Overloads является необязательным в случае, если перегружаемые методы принадлежат одному классу, в этом случае модификатор можно не использовать. Public Sub ShowFoto(ByVal s As String) Me.foto = Image.FromFile(s) End Sub Public Sub ShowFoto(ByVal obj As Object, ByVal s As String) End Sub Рассмотрим случай перегрузки методов класса на примере перегрузки конструктора класса. Предположим, что при создании некоторых экземпляров класса Persona необходимо инициировать все свойства класса (фамилию и пароль), а при создании других экземпляров класса – только одно из свойств (фамилию), ввод пароля будет отложен. В этом случае описание конструкторов класса Persona будет выглядеть следующим образом: Public Class Persona Private _fullName As String Private _personID As Integer Private _password As String Public Sub New(ByVal fullName As String, ByVal password As String) Dim randX As New Random Me.FullName = fullName Me.Password = password _personID = randX.Next(1, 100) End Sub Public Sub New(ByVal fullName As String) МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 8 Dim randX As New Random Me.FullName = fullName _personID = randX.Next(1, 100) End Sub End Class Перегрузка методов класса является разновидностью полиморфизма (многовариантности, многоформенности), так как предполагает различия в списке параметров одноименных методов. Перегрузка является частным проявлением полиморфизма, полиморфизм в общем виде и такого различия не требует. Самостоятельно Добавьте в класс Persona еще один перегруженный метод ToShow, позволяющий выполнять графический вывод свойства FullName на любую поверхность, поддерживающую графическое изображение. На рис. 3 одновременно используются оба перегруженных метода вывода свойства FullName: текстовый и графический. Рис. 3. Использование перегруженных методов текстового и графического вывода свойства FullName В качестве одного из вариантов создания перегруженных методов можно рассмотреть следующий пример. Функция ToShow() для текстового вывода свойства FullName может быть организована, например, так: Public Function ToShow() As String Return FullName.ToUpper End Function Перегруженный метод ToShow() для графического вывода свойства FullName может рисовать объемный текст. Public Sub ToShow(ByVal obj As Object) Dim sb As New SolidBrush(Color.Gray) Dim f As New Font("Tahoma", 18, FontStyle.Bold) Dim g As Graphics = obj.creategraphics g.DrawString(Me.FullName, f, sb, 10, 10) sb.Color = Color.White МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 9 g.DrawString(Me.FullName, f, sb, 8, 8) sb.Dispose() f.Dispose() g.Dispose() End Sub Для организации графического вывода можно придумать что-нибудь оригинальное, например, выпуклый текст. Достигаются подобные эффекты, как вы могли увидеть, простым смещением текста с другим цветом, имитирующим тень. В проекте поместим кнопку и в качестве обработчика события щелчка по кнопке напишем следующий код: Dim p As New Persona("Макдональд Мэтью", "ММ") Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click TextBox1.Text = p.ToShow p.ToShow(PictureBox1) End Sub Задание 4. Создание класса Vector (методов класса) Задача Выполнение действий над трехмерными векторами. Решение Создайте собственный класс вектора. Обсуждение .NET Framework не поддерживает операции с векторами. Необходимо создать класс Vector, включающий методы Add, Subtract, Multiply и DivideBy. Предположительно структура класса может быть следующей: Public Class Vector 'Координаты вектора Private x, y, z As Double 'Конструктор класса Public sub New() End Sub 'Сложение вектора с имеющимся вектором Public function Add() End Function 'Вычитание вектора из имеющегося вектора МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 10 Public function Subtract() End Function 'Умножение имеющегося вектора на скаляр Public function Multiply() End Function 'Умножение имеющегося вектора на вектор ‘Обратите внимание – можно создать перегруженный метод Public function Multiply() End Function 'Деление имеющегося вектора на скаляр Public function DivideBy() End Function 'Определение длины вектора Public function Length() End Function Метод для графического вывода вектора Public Sub ToShow() End Sub End Class Разработайте интерфейс для тестирования класса Vector. Добавьте в описание класса Vector разделяемую переменную для подсчета числа созданных экземпляров класса. Пример интерфейса для тестирования класса Vector представлен на рис. 4. Рис. 4. Пример интерфейса для тестирования класса Vector Примечание При разработке программного кода используйте следующие формулы для Декартовой системы координат: 1. Покоординатное сложение векторов a и b МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 11 a x b x a x b x a b a b y y y y a z bz a z bz 2. Скалярное произведение векторов a•b=a x b x + a y b y + a z b z 3. Векторное произведение векторов a x b=(a y b z -a z b y )i+(a z b x -a x b z )j+(a x b y -a y b x )k 4. Длина вектора l ax2 a y2 az2 5. Прежде чем приступать к рисованию вектора в любом элементе управления, например, в PictureBox, можно выполнить некоторую подготовку, например, перенести отсчет координат в центр PictureBox (см. текст процедуры MapGraphicsWindow) и провести координатную сетку (процедура DrawAxes). Так будет гораздо удобнее, чем отталкиваться от верхней левой точки элемента управления. ‘Процедура преобразования глобальных координат устройства в требуемые вам координаты Public Sub MapGraphicsWindow(ByVal g As Graphics, _ ByVal wxmin As Single, ByVal wymin As Single, _ ByVal wxmax As Single, ByVal wymax As Single, _ ByVal dxmin As Single, ByVal dymin As Single, _ ByVal dxmax As Single, ByVal dymax As Single) ' Преобразуем глобальные координаты центра g.TranslateTransform(-(wxmax + wxmin) / 2, _ -(wymax + wymin) / 2, MatrixOrder.Append) ' Масшабируем Dim sx As Single sx = CSng((dxmax - dxmin) / (wxmax - wxmin)) Dim sy As Single sy = CSng((dymax - dymin) / (wymax - wymin)) g.ScaleTransform(sx, sy, MatrixOrder.Append) ' Преобразуем в центр формы g.TranslateTransform((dxmax + dxmin) / 2, _ (dymax + dymin) / 2, MatrixOrder.Append) End Sub МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 12 Private Sub DrawAxes(ByVal g As Graphics) ' Рисуем координатные оси Dim x As Double Dim y As Double 'Черное перо для координатных осей Dim p_axes As New Pen(Color.Black, 0) g.DrawLine(p_axes, -2, 0, 2, 0) ' Шкала по оси X For x = -2 To 2 Step 0.5 g.DrawLine(p_axes, CSng(x), CSng(-0.1), CSng(x), CSng(0.1)) Next x ' Шкала по оси Y g.DrawLine(p_axes, 0, -2, 0, 2) For y = -2 To 2 Step 0.5 g.DrawLine(p_axes,CSng(-0.1),CSng(y),CSng(0.1),CSng(y)) Next y End Sub Задание 5. Работа с простыми дробями Задача Выполнение математических действий над дробями без преобразования их в десятичную форму (которое может приводить к ошибкам округления). Решение Разработайте собственный класс простой дроби. Обсуждение .NET Framework не поддерживает действий над простыми дробями. Однако создать такой класс совсем несложно. Этот класс будет включать методы Add, Subtract, Multiply и DivideBy. Предложения по именованию свойств и методов класса следующие: Public Class Fraction 'Два компонента любой простой дроби Private Denominator As Integer Private Numerator As Integer 'Создание новой дроби Public sub New() End Sub Методы класса Сложение дроби с имеющейся дробью МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 13 Public Function Add() End Function Вычитание дроби из имеющейся дроби Public Function Subtract() End Function Умножение имеющейся дроби на указанную дробь Public Function Multiply() End Function Деление имеющейся дроби на указанную дробь Public Function DivideBy() End Function End Class Разработайте интерфейс для тестирования класса Fraction. Пример интерфейса для тестирования класса Fraction представлен на рис. 5. Рис. 5. Пример интерфейса для тестирования класса Fraction Обратите внимание на результат запрограммированных действий (в данном случае на рис. 5 выполнено сложение простых дробей). Результат представлен без сокращения дроби и учета ее знака. Все необходимые корректировки внесем позже, при освоении темы, связанной со стандартными интерфейсами. В качестве помощи рассмотрим фрагмент кода описания класса Fraction, создания и применения экземпляров этого класса. Public Class Fraction Public Denominator As Integer Public Numerator As Integer Public Sub New(ByVal numerator As Integer, ByVal denominator As Integer) Me.Numerator = numerator Me.Denominator = denominator End Sub Public Function Add(ByVal fraction As Fraction) As Fraction Return New Fraction(Me.Numerator * fraction.Denominator + fraction.Numerator * Me.Denominator, Me.Denominator * МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 14 fraction.Denominator) End Function Public Function Subtract(ByVal fraction As Fraction) As Fraction Return New Fraction(Me.Numerator * fraction.Denominator fraction.Numerator * Me.Denominator, Me.Denominator * fraction.Denominator) End Function End Class Рассмотрим применение класса в проекте. Кнопка Button1 предназначена для выполнения сложения дробей, кнопка Button2 – вычитания. Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim fraction1 As New Fraction(CInt(TextBox1.Text), CInt(TextBox4.Text)) Dim fraction2 As New Fraction(CInt(TextBox2.Text), CInt(TextBox5.Text)) fraction1 = fraction1.Add(fraction2) TextBox3.Text = fraction1.Numerator TextBox6.Text = fraction1.Denominator End Sub Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click Dim fraction1 As New Fraction(CInt(TextBox1.Text), CInt(TextBox4.Text)) Dim fraction2 As New Fraction(CInt(TextBox2.Text), CInt(TextBox5.Text)) fraction1 = fraction1.Subtract(fraction2) TextBox3.Text = fraction1.Numerator TextBox6.Text = fraction1.Denominator End Sub Задание 6. Наследование конструкторов, свойств и методов базового класса Задача Создавать и использовать в проектах иерархию классов. Решение Создать базовый (родительский) и дочерние классы, связанные механизмом наследования. Обсуждение Одним из наиболее мощных свойств объектно-ориентированных языков является наследование – возможность создавать класс со МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 15 свойствами и методами, которые могут быть использованы в других классах. В терминологии ООП базовый класс называется наследуемым (родительским), а производные от него – наследниками (дочерними). Цель наследования заключается в создании базового класса, в котором инкапсулируются свойства и методы, необходимые множеству производных классов того же типа. На основе базового класса создаются другие классы, которые, как правило, не ограничены свойствами и методами этого класса, а имеют более широкие возможности. При описании классов-наследников, необходимо показать какой класс для них является базовым, это делается с помощью ключевого слова Inherits. Public Class A End class A Public Class B Inherits A End Class B Если следует запретить наследование свойств и методов класса (закрытый класс), то в его описании необходимо добавить ключевое слово NotInheritable. Public NotInheritable Class A Каждый класс может иметь только один базовый (родительский) класс, но сам может являться базовым классом для многих классов. В VB.NET запрещено множественное наследование, но имеется механизм интерфейсов – аналог множественного наследования, который мы изучим позже. В качестве примера рассмотрим иерархию наследования, построенную для классов Number (базовый класс) и двух его наследников RomanNumber (римские цифры) и ArabianNumber (арабские цифры). Иерархия классов изображена на рис. 6. Модель классов построена в редакторе PowerDesigner, который представляет собой профессиональную среду для создания диаграмм классов. Обратите внимание на графическое изображение свойств, объявленных как Public или Private (значки + и -), описание типов данных, и присвоение им начальных значений. Наследование на диаграмме изображается пустой стрелкой, обращенной к базовому классу. Создайте Windows проект, включающий три модуля классов: Number, RomanNumber и ArabianNumber, а также форму с элементами управления для тестирования поведения классов. МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 16 Number + Value : UInt16 - Counter : UInt16 + + + + =0 New () Dispose () Instances () : UInt16 ShowNumber () : String RomanNumber ArabianNumber + New () + New () Рис. 6. Иерархия классов В соответствии и диаграммой классов (рис. 6) опишем иерархию классов программным образом. Класс Number имеет свойство Value, которое описывает значение (число); свойство Value доступно извне, поэтому объявлено с ключевым словом Public: Public Class Number Public Value As Integer Private Shared Counter As Integer = 0 Public Sub New() Value = 0 Counter += 1 End Sub Public Sub New(byval n as integer) Value = n Counter += 1 End Sub Public Sub Dispose() Counter -= 1 End Sub Public Shared ReadOnly Property Instances() As Integer Get Return Counter End Get End Property Public Function ShowNumber() As String Return Me.value.ToString End Function End Class В классе объявлен метод ShowNumber, который выводит на экран число, cодержащееся в свойстве Value класса Number, в текстовом формате. МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 17 Создадим два класса наследника: RomanNumber и ArabianNumber, которые пока будут только наследовать свойства и методы класса Number. Public Class RomanNumber Inherits Number Public Sub New(ByVal n As Integer) MyBase.New(n) End Sub End Class Public Class ArabianNumber Inherits Number Public Sub New(ByVal n As Integer) MyBase.New(n) End Sub End Class Все классы создаются в отдельных модулях, не смотря на то, что они представляют единую иерархию. Уделим должное внимание конструкторам классов наследников. Так как базовый класс Number имеет конструктор New() с параметрами, то в конструкторах классов наследников первой строкой кода должна быть: MyBase.New(параметры). Ключевое слово MyBase позволяет вызвать конструктор базового класса с целью инициализации в классе наследнике той части кода, которую он наследует от базового класса. Далее, в конструкторе производного класса могут инициироваться собственные свойства, если они есть (в нашем случае, пока ничего нет). При обращении к собственным свойствам следует указывать другое ключевое слово (модификатор) — Me. Уже в таком скромном представлении классы RomanNumber и ArabianNumber могут быть протестированы, так как они, так же, как и класс Number, могут содержать число и выводить его в виде текста. Пока все три класса устроены одинаково – изображение числа в разных классах не отличается. Пример тестирования иерархии классов приведен ниже (рис. 7). Самостоятельно Код для тестирования классов напишите самостоятельно. МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 18 Рис. 7. Тестирование иерархии классов Задание 7. Полиморфизм. Переопределенные методы Задача Изменять поведение дочерних классов по отношению к поведению базового класса. Решение Использовать полиморфизм – создавать одноименные методы, выполняющие разные действия. Обсуждение Важной особенностью объектно-ориентированного программирования, связанной с иерархией классов, является возможность послать одинаковые сообщения сразу нескольким объектам различных классов. Объектам предоставляется право выбрать, кто из них будет это сообщение обрабатывать. Другими словами, полиморфизм – это способность объектов, принадлежащих разным классам, реагировать на один и тот же вызов метода с помощью собственной реализации метода. Такие методы называются переопределенными, у классов наследником они помечаются ключевым словом (модификатором) Overrides, а у класса родителя – модификатором Overridable. Например, Public Overridable Function ShowNumber() As String Так будет выглядеть описание метода, подлежащего переопределению, у родительского класса, Public Overrides Function ShowNumber() As String МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 19 А так будет выглядеть сигнатура переопределенного метода у класса наследника. Обратите внимание на полное совпадение сигнатур методов, при перегрузке методов сигнатуры должны быть различными, а при переопределении – сигнатуры могут совпадать. Теперь вернемся к нашему примеру с числами и попробуем переопределить метод вывода числа в соответствии с названием класса. В том, что классы RomanNumber и ArabianNumber унаследовали свойство Value и метод ShowNumber мы убедились. Теперь будем изменять метод ShowNumber у классов наследников (сохраняя сигнатуру метода, что важно) с тем, чтобы вывод чисел соответствовал требованиям классов RomanNumber и ArabianNumber. Public Class Number Public value As Integer Private Shared Counter As Integer = 0 Public Sub New(ByVal n As Integer) value = n Counter += 1 End Sub Public Sub Dispose() Counter -= 1 End Sub Public Shared ReadOnly Property Instances() As Integer Get Return Counter End Get End Property Public Overridable Function ShowNumber() As String Return Me.value.ToString End Function End Class Public Class RomanNumber Inherits Number Public Sub New(ByVal n As Integer) MyBase.New(n) End Sub Public Overrides Function ShowNumber() As String Dim An() As Integer = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1} Dim Rn() As String = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"} Dim i As String = 0 МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 20 Dim N = value Dim Result As String Do While An(i) <= N Result = Result + Rn(i) N = N - An(i) End While i=i+1 Loop While N > 0 Return Result End Function End Class Обратите внимание на модификатор Override в описании переопределенной функции ShowNumber в классе RomanNumber. В базовом класса Number, в описании переопределяемой функции ShowNumber также должен появиться модификатор Overridable. Самостоятельно Переопределите функцию ShowNumber для класса ArabianNumber. Пример тестирования переопределенных методов для классов наследников см. на рис. 8. Рис. 8. Тестирование переопределенных методов классов наследников Задание 8. Практическое использование иерархии классов Задача Использовать созданную иерархию классов в Windows-приложении. МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 21 Решение Разработать и запрограммировать интерфейс приложения, позволяющего выполнять перевод чисел в разные системы счисления. Обсуждение Для применения классов Number, ArabianNumber и RomanNumber разработайте пользовательский интерфейс приложения (один из возможных вариантов приведен на рис. 9). Рис. 9. Пример интерфейса приложения Самостоятельно Используйте созданную иерархию классов для организации работы приложения. Задание 9. Абстрактные классы Задача Создавать верхний уровень иерархии в виде абстрактного класса, не имеющего собственных экземпляров. Решение Использовать специальные модификаторы для описания абстрактного класса (MustInherit и MustOverride). Обсуждение В нашем примере базовый класс Number является общедоступным, то есть может быть инициирован любым кодом, определенным вне заданной иерархии классов. Можно изменить иерархию, сделав базовый класс Number доступным только для производных классов и МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 22 защищенным от остального кода. Для того чтобы создать такой абстрактный класс необходимо добавить ключевое слово MustInherit к оператору описания класса. Методы и свойства абстрактного класса доступны только через экземпляры производных классов, экземпляры собственно абстрактного класса создавать запрещено. Если методы абстрактного класса только обозначены, то есть, описаны их сигнатуры, но не определены операторы, выполняемые методами, то такие методы в описании абстрактных классов должны быть помечены ключевым словом MustOverride. Более того, подобные методы могут иметь только заголовок (сигнатуру), но не иметь ключевых слов End Function или End Sub. Но таким образом описанные методы абстрактного класса должны обязательно быть переопределены в классах наследниках. Самостоятельно 1. Опишите класс Number как абстрактный класс и прокомментируйте сообщения об ошибке, возникшей в предыдущей версии программы (задание 7). 2. Опишите метод ShowNumber() класса Number как метод, который обязательно подлежит переопределению. 3. Опишите класс RomanNumber как закрытый класс, то есть класс, который не должен иметь наследников. 4. Протестируйте поведение классов с учетом внесенных изменений. Задание 10. Абстрактные классы в стандартных библиотеках Задача Рассмотреть применение библиотеках классов .NET. абстрактных классов в стандартных Решение Изучить иерархию классов System.Drawing.Brush. Обсуждение В качестве примера использования иерархии классов, с начальной вершиной в виде абстрактного класса рассмотрим очень красивую по реализации иерархию классов Brush (Кисти). Иерархия классов находится в пространстве имен System.Drawing.Brush, строится на основе базового класса Brush. Класс Brush является абстрактным классом, то есть создавать МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 23 экземпляры этого класса невозможно. В этом можно убедиться, если набрать следующий код Dim br As New Brush (Color.Red) и увидеть сообщение об ошибке. Иерархия имеет несколько классов кистей от простых кистей, до кистей, имеющих довольно сложную фактуру. В качестве примеров для изучения рассмотрим классы SolidBrush, HatchBrush и TextureBrush, хотя, безусловно, и другие классы типа LinearGradientBrush, PathGradientBrush тоже могут стать источником интересных решений интерфейсов. Напомним, что все эти классы являются производными от главного абстрактного класса Brush, который сам непосредственно как инструмент работать не может. В Windows-форме для обработки события щелчка по командной кнопке введите следующий код использования сплошных кистей класса SolidBrush. Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim g As Graphics = CreateGraphics() Dim rect As New Rectangle(10, 10, _ Me.ClientSize.Width - 20, _ Me.ClientSize.Height - 20) 'Создаем зеленую сплошную кисть Dim sbr As Brush = New System.Drawing.SolidBrush(Color.Green) 'Заполняем прямоугольник зеленой кистью. g.FillEllipse(sbr, rect) ' Обрамляем прямоугольник эллипсом в 10 пикселов g.DrawEllipse(New Pen(Color.DarkMagenta, 10), rect) g.Dispose() End Sub Конструктор класса SolidBrush поддерживает и другую модель для получения цвета – FromARGB. В случае применения этой модели цветоразрешения кисти могут быть полупрозрачными. Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click Dim g As Graphics = CreateGraphics() 'Создадим три полупрозначные кисти. Dim trnsRedBrush As New SolidBrush(Color.FromArgb(150, 255, 0, 0)) Dim trnsGreenBrush As New SolidBrush(Color.FromArgb(150, 0, 255, 0)) Dim trnsBlueBrush As New SolidBrush(Color.FromArgb(150, 0, 0, 255)) 'Основание и высота треугольника для позиционирования кругов 'Каждая вершина треугольника является центром одного из кругов. 'Основание равно диаметру круга. Dim triBase As Single = 150 МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 24 Dim triHeight As Single = CSng(Math.Sqrt((3*(triBase*triBase) / 4))) 'Координаты для первого круга Dim x1 As Single = 40 Dim y1 As Single = 40 'Заполняем круги g.FillEllipse(trnsRedBrush,x1,y1,2*triHeight, 2*triHeight) g.FillEllipse(trnsGreenBrush, x1 + triBase / 2, y1 + triHeight, 2 * triHeight, 2 * triHeight) g.FillEllipse(trnsBlueBrush, x1 + triBase, y1, 2 * triHeight, 2 * triHeight) g.Dispose() End Sub Попробуйте запускать проект в разных режимах, поочередно нажимая командные кнопки. Обратите внимание, что на зеленом фоне большого эллипса полупрозрачный красный круг получается коричневым (рис. 10). Класс HatchBrush представляет собой узорную кисть. Класс поддерживает более 50 различных узоров (стилей). Используем стиль HorizontalBrick, который состоит из множества кирпичиков. Не забывайте, что кисть может иметь не только черно-белую составляющую, но и любую пару цветов на ваш выбор (рис. 10). Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click 'Заполним всю форму кирпичами Dim hbr As New HatchBrush(HatchStyle.HorizontalBrick,Color.Gray, Color.Tomato) Dim g As Graphics = CreateGraphics() g.FillRectangle(hbr,0,0,ClientSize.Width, ClientSize.Height) g.Dispose() End Sub МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 25 Рис. 10. Проект «Кисти» Класс TextureBrush. Особенностью текстурных кистей, созданных на основе класса TextureBrush, является использование картинки. Приведем простой пример применения текстурной кисти (рис. 11). Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Dim g As Graphics g = CreateGraphics() Dim img As Image = Image.FromFile("L:\face.JPG") Dim tb As New System.Drawing.TextureBrush(img, New Rectangle(0, 0, 60, 88)) tb.WrapMode = Drawing2D.WrapMode.Tile.TileFlipY g.FillEllipse(tb, 0, 0, 2 * ClientSize.Width \ 3, 2 * ClientSize.Height \ 3) g.FillRectangle(tb, ClientSize.Width \ 3, ClientSize.Height \ 3,ClientSize.Width \ 2, 2 * ClientSize.Height \ 3) g.Dispose() End Sub В этом примере мы создали текстурную кисть, где в качестве картинки служит изображение из графического файла. У текстурных кистей существует несколько режимов заполнения. Например, режим TileFlipY позволяет отразить картинку по горизонтали и размножить ее, заполняя эллипс и прямоугольник. МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 26 Рис. 11. Применение текстурной кисти Обратите внимание, что, несмотря на наложение этих двух фигур, рисунки в них совпадают, не перекрывая друг друга (рис. 11). К слову сказать, текстурные кисти можно вращать, сдвигать, масштабировать. Поставьте соответствующий вызов перед выводом фигур на экран: tb.RotateTransform(-35) Как видите, узор кистей повернулся на тридцать пять градусов. Самостоятельно Используя редактор PowerDesigner, создайте диаграмму иерархии классов Brush. Задание 11. Применение модификаторов доступа в базовых классах Задача Регулировать доступ к отдельным свойствам и методам класса. Решение Использовать специальные модификаторы: Public, Private и Protected. Обсуждение При формировании иерархической структуры классов с использованием технологии наследования важно выбрать те свойства и методы, которые будут доступны в ваших классах. Такой доступ к отдельным элементам класса регулируется с помощью специальных модификаторов Public, Private и Protected. МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 27 Если метод или свойство базового класса объявляют с ключевым словом Public, они будут доступны как для производных классов, так и для кода, определенного за пределами этих классов. Если метод или свойство объявлены как Private, они будут доступны только внутри базового класса. Бывают ситуации, когда нужно разрешить доступ к свойству или методу из всех производных классов, но запретить доступ к ним кода, определенного вне этой иерархии. В данном случае следует применять модификатор доступа Protected. Самостоятельно Исследуйте способность перечисленных модификаторов ограничивать доступ к свойствам и методам базового класса на следующем примере. Добавьте в описание класса Number метод, позволяющий делать графический вывод числа. Изменяя уровень доступа к разработанному методу в классах наследниках с помощью модификаторов Public, Private и Protected, изучите механизмы ограничения доступа к этому методу. Пример графического вывода приведен на рис. 12. Рис. 12. Пример графического вывода экземпляров иерархии Number Задание 12. Затенение свойств и методов базового класса Задача Регулировать доступ к отдельным свойствам и методам базового класса. Решение Использовать специальный модификатор Shadows. МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 28 Обсуждение Когда производный класс наследует метод базового класса, он может затенять (shadowing), а не переопределять его. Метод производного класса, сигнатура которого совпадает с сигнатурой метода базового класса, затеняет метод базового класса. При этом затеняется не только метод базового класса, но и все одноименные перегруженные методы базового класса. Как на практике выполнить затенение рассмотрим на примере нашей иерархии классов Number, ArabianNumber и RomanNumber. Добавим в базовый класс Number Shared-свойство Counter для подсчета количества созданных экземпляров класса. Доступ к свойству обеспечим за счет Shared-метода Instances, определенного с атрибутом ReadOnly. Public Class Number Public value As Integer Private Shared Counter As Integer = 0 Public Sub New(ByVal n As Integer) value = n Counter += 1 End Sub Public Sub Dispose() Counter -= 1 End Sub Public Shared ReadOnly Property Instances() As Integer Get Return Counter End Get End Property В классе наследнике RomanNumber добавим свойство Counter и метод Instance, которые будут затенять одноименные свойство и метод базового класса. Public Class RomanNumber Inherits Number Private Shared Shadows Counter As Integer = 0 Public Sub New(ByVal n As Integer) MyBase.New(n) Me.Counter += 1 End Sub Public Shared Shadows ReadOnly Property Instances() As Integer Get Return Counter End Get МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 29 End Property А в описании еще одного класса наследника не будем добавлять никаких переопределяющих или затеняющих свойств, методов. То есть, этот класс по-прежнему наследует свойство Counter и метод Instances базового класса (рис. 13). Рис. 13. Тестирование метода счета количества созданных экземпляров Public Class ArabianNumber Inherits Number Public Sub New(ByVal n As Integer) MyBase.New(n) End Sub Public Overrides Function ShowNumber() As String Return value.ToString("E") End Function End Class Задание 13. Иерархия классов «Фигуры» Задача Разработать собственную иерархию классов, применив все изученные технологии: абстрактный класс, наследование свойств и методов класса, перегрузка методов класса, переопределение методов класса, затенение методов класса, Shared-свойства класса. Решение Использовать все технологии объектно-ориентированного программирования для проектирования иерархического дерева классов с реализацией наследования, инкапсуляции и полиморфизма. МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE Для наглядности рисования фигур. проекта спроектировать графические 30 методы Обсуждение Разработайте иерархию классов для описания геометрических фигур, например, Окружности, Треугольника, Квадрата, Прямоугольника…. Классы должны описывать свойства фигуры и иметь методы для вычисления площади и периметра фигуры. В качестве базового класса сконструируйте абстрактный класс Shape (Фигура). Для описания свойств классов используйте процедуры описания свойств (Property). Методы абстрактного класса объявите обязательными для переопределения. Класс Square (Окружность) объявите закрытым классом. Спроектируйте интерфейс приложения для исследования работы иерархии классов. Примерный вид интерфейса показан на рис. 14. Рис. 14. Примерный интерфейс приложения Дополните проект еще одной формой, в которой продемонстрируйте графические методы классов, позволяющие изображать фигуры. Для рисования прямоугольника (квадрата) можно использовать методы DrawRectangle или FillRectangle, для рисования нескольких прямоугольников удобнее применить методы DrawRectangles или FillRectangles. При использовании методов с префиксом Fill заданные прямоугольники закрашиваются кистью заданного цвета. В остальных случаях рисуется окантовка фигуры. Вот небольшой пример вывода последовательности синих квадратов по диагонали. Чтобы точно подогнать квадраты под размеры клиентской области формы зададим размер формы равным 400400 пикселов. МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 31 Форма для тестирования графических методов фигур может выглядеть примерно как на рис. 15. Используйте меню для вызова графических методов различных фигур. Рис. 15. Тестирование графических методов Ниже приведен фрагмент кода для рисования квадратов методом FillRectangles. Код не имеет отношения к методом разрабатываемой иерархии, вы можете воспользоваться приемами рисования, показанными в коде программы. Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load ClientSize = New Size(400, 400) End Sub Private Sub MenuItem2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MenuItem2.Click Dim g As Graphics = CreateGraphics() Dim rs As Rectangle() = {New Rectangle(0, 0, 100, 100), _ New Rectangle(100, 100, 100, 100), _ New Rectangle(200, 200, 100, 100), _ New Rectangle(300, 300, 100, 100)} g.FillRectangles(New SolidBrush(Color.Blue), rs) g.Dispose() End Sub МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 32 Метод DrawLine позволяет рисовать линии разного цвета и толщины. При создании линий используйте перо и координаты двух крайних точек. Так как цвет пера можно создать на основе метода FromARGB. То у нас есть возможность создавать линии с разным уровнем прозрачности (рис. 15) Private Sub MenuItem7_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MenuItem7.Click Dim g As Graphics = CreateGraphics() Dim clr As New Color Dim p As New Pen(clr, 5) g.DrawLine(New Pen((Color.Red), 10), 20, 5, 20, 215) g.DrawLine(New Pen((Color.Blue), 10), 190, 5, 190, 215) 'выводим ряд горизонтальных линий с изменяющей прозрачностью Dim i As Integer For i = 10 To 210 Step 20 p.Color = clr.FromArgb(255 - i, 0, i, 255) g.DrawLine(p, 10, i, 200, i) Next End Sub Для рисования эллипсов (рис. 15) используйте методы DrawEllipse или FillEllipse. Метода, рисующего последовательность эллипсов аналогичного методу DrawRectangles, в VB.NET не существует. Но это не трудно реализовать самостоятельно, в цикле рисуя вложенные эллипсы. Приведем фрагмент кода, который вы сможете использовать для формирования метода рисования эллипса в вашем классе. Private Sub MenuItem3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MenuItem3.Click Dim g As Graphics = CreateGraphics() Dim p As New Pen(Color.Green) Dim i As Integer For i = 0 To 100 Step 5 g.DrawEllipse(p, 10 + i, 10, 200 - 2 * i, 200) Next g.Dispose() End Sub Для рисования секторов (рис. 15) можно использовать метод FillPie. Вот так, приблизительно, это реализуется программным способом. Private Sub MenuItem4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MenuItem4.Click Dim g As Graphics = CreateGraphics() Dim sb As New SolidBrush(Color.Yellow) Dim i As Integer For i = 0 To 359 Step 120 МЕЖДУНАРОДНЫЙ БАНКОВСКИЙ ИНСТИТУТ INTERNATIONAL BANKING INSTITUTE 33 g.FillPie(sb, 30, 30, 110, 110, i, 60) Next g.Dispose() End Sub Для рисования дуг используется метод DrawArc. Двойной вызов метода приводит к следующим результатам (рис.15). Private Sub MenuItem5_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MenuItem5.Click Dim g As Graphics = CreateGraphics() g.DrawArc(New Pen(Color.Red), 100, 70, 300, 300, 90, -180) g.DrawArc(New Pen(Color.Red), 150, 70, 200, 300, 90, -180) g.Dispose() End Sub И, наконец, многоугольник. Многоугольники задаются массивом точек, которые соединяются между собой прямыми отрезками с помощью метода DrawPolygon, причем первая точка массива автоматически соединяется с последней его точкой (рис. 15). Private Sub MenuItem6_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MenuItem6.Click Dim g As Graphics = CreateGraphics() Dim apts(9) As Point 'Задаем вершины многоугольника apts(0).X = 180 : apts(0).Y = 70 apts(1).X = 200 : apts(1).Y = 130 apts(2).X = 260 : apts(2).Y = 130 apts(3).X = 220 : apts(3).Y = 170 apts(4).X = 240 : apts(4).Y = 230 apts(5).X = 180 : apts(5).Y = 190 apts(6).X = 120 : apts(6).Y = 230 apts(7).X = 140 : apts(7).Y = 170 apts(8).X = 105 : apts(8).Y = 130 apts(9).X = 160 : apts(9).Y = 130 g.DrawPolygon(Pens.Crimson, apts) End Sub