|
|||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||
|
Нетривіальне
застосування інтерфейсів
Інтерфейси
також можуть оголошуватися похідними від інших інтерфейсів. В цьому випадку інтерфейс
просто доповнюється новими членами. Припустимо, в нашій системі обліку кадрів
провідним програмістам надано право вирішувати модернізацію комп'ютерів
своїх підлеглих. У програмі це моделюється методом Upgradehardware: Public
Interface Ileadprogrammer Inherits
Head Public Function Upgradehardware(aperson As Programmer) End Interface В цьому випадку
реалізація Ileadprogrammer вимагає додаткового виконання контракту інтерфейсу
Head. На відміну
від класів, які можуть успадковувати лише від одного базового класу, інтерфейс
може бути оголошений похідним від декількох інтерфейсів: Public
Interface Ileadprogrammer Inherits Head.Inherits
Icodeguru Public Function Upgradehardware(aperson As Programmer) End
Interface Оскільки інтерфейс може успадковувати
від декількох інтерфейсів, реальна ситуація, при якій в нім
потрібно буде визначити два однойменні методи, що належать до різних
інтерфейсів, — наприклад, якщо інтерфейси Head і Icodeguru містять методи з
ім'ям Spendmoralefund. В цьому випадку ви не зможете звернутися до одного з цих
методів через змінну типу, що реалізовує такий інтерфейс: Dim tom As New Leadprogrammer("Tom", 65000) tom.SpendMoraleFund(500) Інтерфейс
повинен указуватися явно, як в наступному фрагменті: Dim tom As New
Leadprogrammer("Tom", 65000) Dim
acodeguru As Icodeguru acodeguru = tom acodeguru.SpendMoraleFund(500)
Вибір
між інтерфейсами і спадкоємством
Хоча на
перший погляд інтерфейси чимось нагадують базові класи, від цієї аналогії
більше шкоди, чим користі. Абстрактний клас може містити реалізовані методи,
а в інтерфейсі вони недопустимі. Абстрактні базові класи створюються тільки
в результаті ретельного аналізу функціональності з виділенням найпримітивнішого
загального предка, і ні по якій іншій причині. Інтерфейси
існують поза ієрархією спадкоємства, і в цьому їх гідність. Ви втрачаєте можливість
автоматичного використання існуючої коди, але взамен набуваєте свободи
вибору власної реалізації контракту. Інтерфейси використовуються в тих випадках,
коли ви хочете показати, що поведінка класу повинна відповідати певним
правилам, але фактична реалізація цих правил залишається на розсуд класу.
У .NET структури не можуть успадковувати ні від чого, окрім Object, але вони можуть
реалізовувати інтерфейси. Нарешті, в .NET інтерфейси стають єдиним
рішенням за ситуації, коли два класи володіють схожою поведінкою, але не
мають загального предка, окремими випадками якого вони б були.
Найважливіші
інтерфейси .NET Framework
Описати всі
інтерфейси .NET Framework на декількох сторінках неможливо, але хоч би отримати
деяке уявлення про них цілком реально. Інтерфейси Icloneable і Idisposable
володіють особливою декларативною функцією — реалізовуючи їх, ви тим самим заявляєте,
що ваш клас володіє якійсь стандартною функціональністю, присутньою в
багатьох класах.
Далі в цьому
розділі розглядаються базові інтерфейси для побудови спеціалізованих колекцій.
Якщо ви пам'ятаєте, з якими труднощами була зв'язана реалізація
циклів For-each в Vb6, вони стануть для вас справжнім подарунком!
Як було показано в
розділі «Memberwiseclone», клонування об'єкту, що містить внутрішні
об'єкти, викликає немало проблем. Розробники .NET дають вам можливість повідомити
про те, що дана можливість реалізована у вашому класі. Для цієї мети
використовується декларативний інтерфейс Icloneable, що складається з єдиної
функції Clone: Public
Interface Icloneable Function Clone() As Object End
Interface
Цей інтерфейс (а отже, і метод Clone) реалізується в тому випадку, якщо ви
хочете надати користувачам свого класу засобу для клонування екземплярів.
Далі ви самі вибираєте фактичну реалізацію методу Clone — не виключено, що
вона зводитиметься до простого виклику Memberwiseclone. Як було сказано вище,
Memberwisecl one нормально клонує екземпляри, поля яких відносяться до структурного
типу або є незмінними (такі, як String). Наприклад, в класі Empl
oyee клонування екземплярів може здійснюватися методом Clone, оскільки всіма
полями є або рядки, або значення структурних типів. Таким чином,
реалізація Ic1 опеаи е,для класу Empl oyee може виглядати так: Public Class Employee Implements Icloneable Public Function Clone() As Object _ Implements Icloneable.Clone Return Ctype(Me.MemberwiseClone, Employee) End
Function '
І так далі End Class У класах, що містять
внутрішні об'єкти, реалізація методу Clone зажадає значно великих
зусиль (хоча в розділі 9 описаний прийом, що дозволяє досить просто вирішити
цю задачу в більшості випадків). Так, в приведеному вище класі Embeddedobject
необхідно клонувати внутрішній масив, не обмежуючись простим копіюванням. Як це зробити?
Дуже просто. Оскільки клас Array реалізує інтерфейс Icloneable, він повинен
містити метод для клонування масивів. Залишається лише викликати цей метод
в потрібному місці. Нижче приведена версія класу Ет-beddedobjects з реалізацією
Icloneabl e (ключові рядки виділені жирним шрифтом): Public Class Embeddedobjects Implements Icloneable Private m_ma() As String Public Sub New(Byval
anarray() As String) m_data = anarray End
Sub Public Function Clone() As Object Implements Icloneable.Clone Dim temp()As
String temp = m_data.Clone
' Клонувати масив Return New Embeddedobjects(temp) End Function Public Sub Displaydata() Dim temp
As String For Each temp In m_data Console.WriteLine(temp) Next End Sub Public Sub Changedatacbyval newdata As String) m_data(0) = newdata End Sub End Class
Вище вже згадувалося про
те, що метод Finalize не забезпечує надійного звільнення ресурсів, що
не знаходяться під управлінням складальника сміття. У програмуванні .NET
у цього завдання існує загальноприйняте рішення — клас реалізує інтерфейс Idisposable
з єдиним методом Dispose, що звільняє зайняті ресурси: Public
Interface Idisposable Sub Dispose() End Interface Отже, запам'ятаєте
наступне правило: Якщо ваш
клас використовує інший клас, реалізовуючий Idisposable, то в кінці роботи з
ним необхідно викликати метод Dispose. Як буде
показано в розділі 8, метод Dispose повинен викликатися в кожному графічному застосуванні,
залежному від базового класу Component, оскільки це необхідно для звільнення
графічних контекстів, використовуваних всіма компонентами.
Колекцією (collection)
називається об'єкт, призначений для зберігання інших об'єктів. Колекція
містить методи для включення і видалення внутрішніх об'єктів, а також звернення
до них в різних варіантах — від простої індексації, як при роботі з
масивами, до складної вибірки по ключу, як в класі Hashtable, представленому в
попередньому розділі. .NET Framework містить немало корисних класів колекцій. Розширення
цих класів за допомогою спадкоємства дозволяє будувати спеціалізовані
колекції, безпечні по відношенню до типів. Та все ж при нетривіальному використанні
вбудованих класів колекцій необхідно знати, які інтерфейси в них реалізовані.
Декілька найближчих розділів присвячено стандартним інтерфейсам колекцій.
For
Each і інтерфейс lenumerable
Підтримка For-each в класах Vb6 була недостатньо інтуїтивною, а її синтаксис
сприймався як щось досконале чужорідне (ми згадували про це в розділі 1). У VB .NET
існують два способи організації підтримки For-each в класах колекцій. Перший
метод вже був продемонстрований вище: новий клас визначається похідним від
класу з підтримкою For-each і автоматично успадковує його функціональність. Зокрема,
цей спосіб застосовувався для класу Empl oyees, похідного від класу System.
Collections. Collectionbase. Другий спосіб,
заснований на самостійній реалізації інтерфейсу Ienumerable, забезпечує
максимальну гнучкість. Визначення інтерфейсу виглядає таким чином: Public
Interface lenumerable Function Getenumerator() As Enumerator End
Interface При реалізації lenumerable
клас реалізує метод Getenumerator, який повертає об'єкт Ienumerator, що
забезпечує можливість перебору в класі. Метод переходу до наступного елементу
колекції визначається саме в інтерфейсі Ienumerator, який визначається
таким чином: Public
Interface lenumerator Readonly
Property Current As Object Function Movenext()
As Boolean Sub Reset () End Interface У циклі For-each
перебір ведеться тільки в одному напрямі, а елементи доступні тільки для читання.
Цей принцип абстрагований в інтерфейсі lenumerator — в інтерфейсі присутній
метод для переходу до наступного елементу, але немає методів для зміни даних.
Крім того, в інтерфейс Ienumerator повинен входити обов'язковий метод для переходу
в початок колекції. Зазвичай цей інтерфейс реалізується способом включення (containment):
у колекцію упроваджується спеціальний клас, якому передоручається виконання
трьох інтерфейсних методів (один з lenumerable і два з Ienumerator). Нижче
приведений приклад колекції Employees, побудованої «на порожньому місці».
Звичайно, клас виходить складнішим, ніж при простому спадкоємстві від System. Collections.
Collectionbase, та зате він володіє набагато більшими можливостями. Наприклад,
замість послідовного повернення об'єктів Employee можна використовувати сортування
по довільному критерію: 1 Public
Class Employees 2 Implements
Ienumerable.IEnumerator 3 Private m_employees()
As Employee 4
Private m_index As Integer = -1 5
Private m_count As Integer = 0 6 Public Function
Getenumerator() As lenumerator _ 7 Implements
lenumerable.GetEnumerator 8 Return
Me 9 End
Function 10 Public Readonly
Property Current() As Object _ 11 Implements
Ienumerator.Current 12
Get 13 Return m_employees(m_index) 14 End
Get 15 End
Property 16 Public Function
Movenext() As Boolean _ 17 Implements
lenumerator.MoveNext 18 If m_index
< m_count Then 19 m_index +=
1 20
Return True 21
Else 22
Return False 23 End
If 24 End
Function 25 Public Sub
Reset() Implements Ienumerator.Reset 26
m_index = 0 27 End
Sub 28 Public Sub
New(Byval theemployees() As Employee) 29 If
theemployees Is Nothing Then 30 Msgbox("No
items in the collection") 31 ' Ініціювати
виключення - див. розділ 7 32 ' Throw New
Applicationexception() 33
Else 34 m_count =
theemployees.Length - 1 35
m_employees = theemployees 36 End
If 37 End
Sub 38 End Class Рядок 2
повідомляє про те, що клас реалізує два основні інтерфейси, використовуваних при
роботі з колекціями. Для цього необхідно реалізувати функцію, яка повертає
об'єкт lenumerator. Як видно з рядків 6-9, ми просто повертаємо поточний об'єкт
Me. Втім, для цього клас повинен містити реалізації членів Ienumerable;
вони визначаються в рядках 10-27. Нижче приведена
невелика тестова програма. Передбачається, що Publiс-класс Employee входить
в рішення: Sub Main() Dim torn As
New Emplpyee("Tom". 50000) Dim sally As
New Employee("Sally". 60000) Dim joe As New
Employee("Joe", 10000) Dim theemployees(l)
As Employee theemployees(0)
= torn theemployees(1)
= sally Dim myemployees
As New Employees(theemployees) Dim
aemployee As Employee For Each
aemployee In myemployees Console.WriteLine(aemployee.TheName)
Next Console.ReadLine() End Sub
|
|
|||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||