|
|||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||
|
Абстрактні
базові класи
На стадії проектування
спадкових зв'язків в програмі часто з'ясовується, що багато класів
володіють цілим рядом схожих рис. Наприклад, позаштатні співробітники не відносяться
до постійних працівників, але і ті та інші володіють рядом загальних атрибутів —
ім'ям, адресою, кодом платника податків і так далі Було б логічне виділити
всі загальні атрибути в базовий клас Payabl eent i ty. Цей прийом, званий
факторингом, часто використовується при проектуванні класів і дозволяє
довести абстракцію до її логічного завершення. У класах, отриманих в
результаті факторингу, деякі методи і властивості неможливо реалізувати, оскільки вони
є загальними для всіх класів в ієрархії спадкоємства. Наприклад, клас Payabl
eent i ty, від якого створюються похідні класи штатних і позаштатних працівників, може
містити властивість з ім'ям TAXID. Зазвичай в процедурі цієї властивості слід
було б організувати перевірку коди платника податків, але для деяких
категорій позаштатних працівників ці коди мають особливий формат. Отже,
перевірка цієї властивості має бути реалізована не в базовому класі Payabl eentity,
а в похідних класах, оскільки лише вони знають, як повинен виглядати правильний
код. У таких ситуаціях зазвичай
визначається абстрактний базовий клас. Абстрактним називається клас,
що містить хоч би одну функцію з ключовим словом Mustoverride; при цьому
сам клас позначається ключовим словом Mustlnherit. Нижче показано, як може виглядати
абстрактний клас Payabl eentity: Public Mustlnherit Class Payableentity Private m_name As String Public Sub New(Byval
itsname As String) m_name = itsname End Sub Readonly Property
Thename()As String Get Return m_name End Get End
Property Public Mustoverride Property TAXID()As String End Class Звернете
увагу: властивість TAXID, помічена ключовим словом Mustoverride, тільки оголошується
без фактичної реалізації. Члени класів, помічені ключовим словом Mustoverride,
складаються з одних заголовків і не містять команд End Property, End Sub і End
Function. Доступне тільки для читання властивість Thename при цьому реалізована;
з цього виходить, що абстрактні класи можуть містити як абстрактні, так
і реалізовані члени. Нижче приведений приклад класу Єгор! оуєе, похідного
від абстрактного класу Payableentity (ключові рядки виділені жирним шрифтом): Public
Class Employee Inherits
Payableentity Private
m_salary As Decimal Private
m_taxid As String Private Const
LIMIT As Decimal = 0.1d Public Sub Newcbyval
thename As String, Byval cursalary As Decimal. Byval TAXID As String) Mybase.New(thename) m_salary = cursalary m_taxid
= TAXID End Sub Public Overrides
Property TAXID() As String Get Return m_taxid End
Get Set(Byval Value As String) If Value.Length
<> 11 then ' Див. розділ
7 Else m_taxid = Value End If End Set End
Property Readonly Property
Salary() As Decimal Get Return Myclass.m_Salary End
Get End
Property Public Overridable Overloads Sub Raisesalary(Byval Percent As Decimal) If Percent >
LIMIT Then '
Операція заборонена - необхідний пароль Console.WriteLineC'NEED
PASSWORD TO RAISE SALARY MORE " & _ "THAN LIMIT!!!!")
Else m_salary =(1d + Percent) * m_salary End
If End Sub Public Overridable
Overloads Sub Raisesalary(Byval Percent As Decimal. Byval
Password As String) If Password ="special" Then m_salary MID + Percent) * m_salary End
If End Sub End Class Перший ключовий рядок
розташований усередині конструктора, який тепер повинен викликати конструктор абстрактного
базового класу для того, щоб правильно задати ім'я. У другому виділеному
фрагменті визначається елементарна реалізація для властивості Taxld, оголошеної
з ключовим словом Mustoverride (у приведеному прикладі нове значення властивості
не перевіряється, як слід було б зробити в практичному прикладі). Нижче приведена
процедура Sub Mai n, призначена для тестування цієї програми: Sub Main() Dim tom As New
Employee("Tom". 50000. "111-11-1234") Dim sally As
New Programmed "Sally", 150000. "111-11-2234".) Console.Wri
teli ne(sa1ly.TheName) Dim ouremployees(l)
As Employee ouremployees(0)
= tom ouremployees(l)
= sally Dim
anemployee As Employee For Each anemployee
In ouremployees anemployee.RaiseSalary(0.lD) Console.WriteLine(anemployee.TheName & "has tax id " & _ anemployee.TaxID
& ".salary now is " & anemployee.Salary()) Next Consol e.ReadLine()
End Sub У програмі
неможливо створити екземпляр класу, оголошеного з ключовим словом Mustlnherit.
Наприклад, при спробі виконання наступної команди: Dim Nogood As New Payableentity("can't do") компілятор
виводить повідомлення про помилку: Class 'Payableentity'
is not creatable because it contains at least one member marked as 'Mustoverride'
that hasn't been overridden. Проте
об'єкт похідного класу можна привласнити змінній або контейнеру абстрактного
базового класу, що дає можливість використовувати в програмі поліморфні виклики: Dim torn As New Employee("Tom". 50000, "123-45-6789") Dim whotopay(13)
As Payableentity whotopay(0) = tom
При використанні
класів колекцій .NET Framework (таких, як Arraylist і Hashtable) виникає
несподівана проблема: ці класи призначені для зберігання узагальненого типу
Object, тому прочитані з них об'єкти завжди приходітся перетворювати
до початкового типу функцією Стуре. Також виникає небезпека того, що
хто-небудь збереже в контейнері об'єкт іншого типу і спроба виклику Стуре завершиться
невдачею. Проблема вирішується використанням колекцій з сильною типізацією
— контейнерів, що дозволяють зберігати об'єкти конкретного типу і
типів, похідних від нього. Хорошим прикладом
абстрактного базового класу .NET Framework є клас Collectionbase. Класи,
похідні від Coll ectionbase, використовуються для побудови колекцій з сильною
типізацією (перш ніж створювати власні класи колекцій, похідні від
Coll ectionbase, переконаєтеся в тому, що потрібні класи відсутні в просторі
імен System.Collections.Specialized). Колекції, безпечні по відношенню до типів,
будуються на основі абстрактного базового класу System. Collections. Collectionbase;
від вас лише потрібно реалізувати методи Add і Remove, а також властивість Item.
Зберігання даних у внутрішньому списку реалізоване на рівні класу System. Collections.
Collectionbase, який і виконує решту всіх операцій. Розглянемо
приклад створення спеціалізованих колекцій (передбачається, що проект містить
клас Employee або посилання на нього): 1 Public
Class Employees 2 Inherits System.Col
lections.CollectionBase 3 ' Метод Add
включає в колекцію тільки об'єкти класу Employee. 4 ' Виклик передоручається
методу Add внутрішнього об'єкту List. 5 Public Sub
Addtbyval aemployee As Employee) 6 List.Add(aemployee) 7 End
Sub 8 Public Sub
Remove(Byval index As Integer) 9 If index >
Count-1 Or index < 0 Then 10 ' Індекс
за межами інтервалу, ініціювати виключення (розділ 7) 11 Msgbox("Can't
add this item")' Msgbox умовно замінює виключення 12
Else 13 List.RemoveAt(index) 14 End
If 15 End
Sub 16 17
Default Public Readonly
Property Item(Byval index As Integer) As Employee 18
Get 19 Return Ctype(List.Item(index).
Employee) 20 End
Get 21 End
Property 22 End
Class У рядках 5-7 абстрактний
метод Add базового класу реалізується передачею виклику внутрішньому об'єкту List;
метод приймає для включення в колекцію тільки об'єкти Empl oyee. У
рядках 8-10 реалізований метод Remove. Цього разу ми також використовуємо властивість Count внутрішнього
об'єкту List, щоб переконатися в тому, що об'єкт, що видаляється, не
знаходиться перед початком або після кінця списку. Нарешті, властивість Item реалізується
в рядках 17-21. Воно оголошується властивістю за умовчанням, оскільки користувачі зазвичай
чекають від колекцій саме такої поведінки. Властивість оголошується доступною тільки
для читання, щоб додавання нових елементів в колекцію могло здійснюватися тільки
методом Add. Звичайно, властивість можна було оголосити і доступним для читання/запису,
але тоді було б потрібно додатковий код для перевірки індексу
елемен, що додававсята.
Наступний фрагмент перевіряє роботу спеціалізованої колекції; неприпустима
операція включення нового елементу (у рядку, виділеному жирним шрифтом) закоментована: Sub Main() Dim torn As
New Employee("Tom", 50000) Dim sally As
New Employee("Sally", 60000) Dim myemployees
As New Employees() myemployees.Add(tom) myemployees.Add(sally) ' myemployees.Add("Tom")
Dim
aemployee As Employee For Each
aemployee In myemployees Console.WriteLine(aemployee.TheName) Next Console. Readline() End Sub Спробуйте
прибрати коментар з рядка myempl oyees. Add("Tom"). Програма перестане
компілюватися, і ви отримаєте наступне повідомлення про помилку: C:\book to comp \chapter 5\employeesclass\employeesclass\modulel.vb(9): A value of type
'String'cannot be converted to 'Employeesclass.Employee'.
Вся
робота .NET Framework (а отже, і VB .NET) заснована на тому, що кожен
тип є похідним від кореневого класу Object, загального предка всіх
класів (у ООП такі класи іноді називаються космічними (space) базовими
класами). До класу Object сходять всі типи, як посилальні (екземпляри класів), так
і структурні (числові типи і дати, перераховувані типи і структури). Зокрема,
з цього виходить, що будь-якій функції, одержуючій параметр типу Object, можна
передати параметр довільного типу (оскільки головне правило спадкоємства,
згадуване на початку розділу, вимагає, щоб змінна похідного типу могла
використовуватися в будь-якому контексті замість змінної базового типу). Клас Object
містить ряд вбудованих логічних функцій, призначених для перевірки типу
об'єктною змінною:
Нащадки класу
Object діляться на дві категорії: структурні типи, похідні від System. Val
uetype (базовий клас всіх структурних типів), і посилальні типи, похідні
безпосередньо від Object. Щоб дізнатися, чи належить деякий тип до категорії
структурних типів, скористайтеся перевіркою наступного вигляду: Sub Maine) Dim а As
Integer = 3 Console.Writel_ine("а
is а value type is " & Isvaluetype(a)) Console. Readline() End Sub Function Isvaluetype(Byval
thing As Object) As Boolean Return (Typeof (thing) Is System.ValueType) End Function
Оскільки клас Object є загальним предком всіх типів VB .NET, мабуть, що
вам доведеться часто використовувати (або перевизначати) методи цього класу. Основні
методи Object описані в декількох найближчих розділах.
У класі
Object підтримуються дві версії Equals — загальна і звичайна. Загальна версія має
наступний синтаксис: Overloads Public Shared Function Equals(0bject. Object) As Boolean Приклад використання: Equals(а. b) Синтаксис
звичайної версії: Overloads Over-ridable
Public Function Equals(Object) As Boolean Приклад використання: а.Equals(b)
Обидві версії
методу Equal s перевіряють, чи володіють два об'єкти однаковими даними, але ви
маєте бути готові перевизначити Equals, якщо цього вимагає специфіка вашого
класу. Не забувайте, що загальні члени класу не перевизначаються, тому перевизначення
допускається лише для звичайної (не загальною) версії Equal s. Наприклад,
якщо у вашій програмі передбачено два способи представлення деякого структурного
типу, поклопочіться про те, щоб ця обставина враховувалася методом Equals
(саме так розробники VB .NET поступили з класом String, хоча, строго кажучи,
цей клас не відноситься до структурних типів). У класі
Object також передбачений загальний (і тому не перевизначуваний) метод Referenceequals.
Метод Referenceequals перевіряє, чи представляють дві змінні один екземпляр.
Наприклад, як показує наступний фрагмент, для двох рядків а і b вираз
а.Equals(b) може бути істинним, а вираз Reference-equals (а. b) — помилковим: Sub Main() Dim а As
String = "hello" Dim b As
String = "Hello" Mid(b.l.d= "h" Console.Writeline("Is
а.Equals(b)true?" & а.Equals(b)) Console.WriteLine("Is Referenceequals(a.b) true?" & _ Referenceequals(a.b)) Console. Readline() End Sub Результат
показаний на мал. 5.4.
Мал.
5.4. Відмінності між
методами Equals і Referenceequals
|
|
|||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||