|
|||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||
|
Знову про властивості
Принципова
відмінність в роботі властивостей Vb6 і VB .NET полягає в тому, що секції Get і
Set тепер повинні володіти однаковим рівнем доступу. Визначати властивості з
секціями Public Get і Private Set в VB .NET не вирішується. Крім
того, в Vb6 властивість не могла змінюватися в процедурі, навіть якщо воно було
передане по посиланню (тобто з ключовим словом Byref). У VB .NET властивості, передані
по посиланню, можуть змінюватися. Але найпринциповіша зміна відноситься до властивостей за умовчанням. У колишніх версіях VB існувала концепція властивостей за умовчанням, яка на перший погляд здавалася дуже зручною. На практиці властивості за умовчанням часто ставали причиною помилок в програмах. Наприклад, що означає наступна команда? Me.Text1
= Text2 Відстежуючи помилки,
що виникають в подібних командах, досвідчені користувачі VB з'ясовували, що
ця команда задає властивості Text текстового поля з ім'ям Textl значення змінної
Text2. Властивості за умовчанням не тільки ставали істоч
нікому помилок в програмах, але і вимагали, щоб при привласненні об'єктів
використовувалося ключове слово Set, оскільки привласнення об'єктам потрібно було відрізняти від
привласнення властивостям. У VB .NET проблема властивостей за умовчанням вирішується просто — вони
дозволені тільки там, де це дійсно виправдано, а саме при використанні
параметрів. Допустимо, у вас є кеш-таблиця atable; при вибірці значень було б
зручно використовувати синтаксис виду atable("thekey"), але це можливо
лише в тому випадку, якщо Item є властивістю за умовчанням для класу
Hashtable. Властивості за умовчанням оголошуються в класі з ключовим словом Default,
причому це допускається лише для властивостей, одержуючих мінімум один параметр. Якщо
властивість за умовчанням перевантажується, всі переобтяжені версії також позначаються
ключовим словом Default. Властивості за умовчанням найчастіше використовуються за ситуації, коли
у об'єкту є властивість, значення якої повертається у вигляді масиву або
іншого об'єкту, здатного вміщати декілька величин (наприклад, хэш-таблицы).
Припустимо, у вас є клас Sal es і властивість Inyear, яка по отриманому
індексу повертає число (об'єм продажів): Public
Class Sales Private m_sales() As Decimal = {100, 200. 300} Default Public Property Inyear(Byval theyear As Integer) As Decimal Get Return m_sales(theyear) End Get Set(Byva1 Value
As Decimal) m_sales(theyear)=Value End Set End Property ' Решта коду
класу End Class Властивість за умовчанням дозволяє використовувати
конструкції вигляду Dim oursales As New Sales() Console.WriteLine(oursa1es(1)) замість Dim oursales
As New Sales() Console.WriteLi
ne(oursales.InYear(1)) Або, наприклад, ви можете написати oursales (2)
= 3000 замість oursales.InYear(2)
= 3000
На перший
погляд здається, що властивості дуже схожі на відкриті поля екземплярів. Якщо
оголосити в класі А відкрите поле з ім'ям evil, на нього можна послатися за
допомогою конструкції A.evil; ніщо не указує на те, що властивість реалізована
у вигляді відкритої змінної. Може, визначити відкрите поле і позбавитися від
клопоту за визначенням процедур Get і Set? Не піддайтеся
спокусі. Інкапсуляцію даних не варто порушувати без вагомих причин (а ще
краще —те порушувати ніколи!). Але
інкапсуляцію можна випадково порушити і іншими способами — наприклад, якщо не стежити за
повертаними значеннями властивостей. Яким чином? Якщо полем є змінний об'єкт
(наприклад, масив), повернення його у вигляді значення властивості приведе до порушення
інкапсуляції, оскільки зовнішній код зможе змінити стан поля екземпляра
через отриману об'єктну змінну. У таких ситуаціях слід створити клон
поля (клонування об'єктів розглядається в розділі 5). Мораль:
Властивості не повинні повертати змінні об'єкти, які є змінними
класів.
Змінні
класу (у тому числі і закриті поля), оголошені за межами його методів або
властивостей, доступні для всіх членів класу. Змінні, оголошені в методі або
властивості, є локальними по відношенню до цього методу або властивості. Таким чином,
змінні рівня модуля є глобальними по відношенню до екземплярів класів.
Приклад: Module
Modulel Dim aglobal As Integer = 37 Sub Main() Dim ana As New
А() Dim ab As New
B() Console. Readline() End
Sub Public Class
A Sub New() aglobal =aGlobal +17 Console.WriteLine(aglobal) End Sub End
Class Public Class
В Sub New() Console.WriteLine(aglobal) End Sub End Class End Module В даному випадку
ціла змінна aglobal визначається на рівні модуля, тому зміни, що
вносяться до aglobal класом А, будуть сприйняті класом В. Іспользовать змінні
рівня модуля не рекомендується, — вся взаємодія між класами має бути
реалізоване на рівні обміну повідомленнями!
У програмах VB .NET
нерідко зустрічаються ситуації, коли у вас є два класи: «зовнішній» і «внутрішній»,
такий, що фактично належить першому. Вкладені (nested)
класи зазвичай виконують допоміжні функції, і їх код має сенс лише в
контексті зовнішнього класу. Існує хороше емпіричне правило: якщо при
прогляданні зовнішнього класу код вкладеного класу можна скрутити у вікні програми
і це не утруднить розуміння логіки зовнішнього класу, значить, робота вкладеного
класу організована правильно. Звичайно, використання вкладених класів завжди
приводить до деякого порушення інкапсуляції — вкладений клас може звертатися
до закритих членів зовнішнього класу (але не навпаки!). Якщо ця обставина
враховується в архітектурі вашого застосування, не варто приділяти йому особливої уваги,
оскільки внутрішній клас всього лише є спеціалізованим членом зовнішнього
класу.
Практичне використання вкладених класів на прикладі зв'язаного списку
Вкладені класи найчастіше застосовуються в реалізаціях різних структур даних.
До найпоширеніших структур даних належить зв'язаний список. Він є
ланцюжком посилань, який дозволяє легко переходити від поточного об'єкту до
наступного, проте пошук завжди починається з конкретного посилання. Застосування вкладених
класів в реалізації зв'язаного списку виглядає цілком природно, оскільки
код об'єктів-посилань не представляє інтересу для користувачів класу Linkedlist,
а об'єкти Link не можуть існувати незалежно від об'єкту Linkedlist, що
містить їх. Нижче приведена
дуже проста реалізація класу для роботи із зв'язаними списками. Проглянете
її, а потім ми детально проаналізуємо лістинг. Звернете увагу на важливий
рядок, виділений жирним шрифтом (рядок 49); у ній використовується нетривіальна
особливість об'єктно-орієнтованого програмування, про яку буде розказано
нижче. 1 Option
Strict On 2 Module Modulel
3 Sub Main() 4 Dim alinkedlist
As New Linkedlistc'first link") 5 Dim aalink
As Linkedlist.Link 6 alink = alinkedlist.MakeLink(alinkedlist.GetFirstLink,"second
link") 7 alink = alinkedlist.MakeLink(alink,"third
link") 8 Console.WriteLine(alinkedlist.GetFirstLink.MyData) 9 alink = alinkedlist.GetNextLink(alinkedlist.GetFirstLink) 10 Console.WriteLine(alink.MyData) 11 Console.WriteLine(alink.NextLink.MyData) 12 Console.
Readline() 13 End
Sub 14
Public Class Linkedlist 15
Private m_currentlink As Link 16
Private m_firstlink As Link 17 Sub New(Byval
thedata As String) 18 m_currentlink
= New Link(thedata) 19
m_firstlink = in_currentlink 20 End
Sub 21 Public Function
Makelink(Byval currentlink As Link.ByVal 22 thedata As
String) As Link 23 m_currentlink
=New Link(currentlink.theData) 24
Return m_currentlink 25 End
Function 26 Public Readonly
Property Getnextlink(Byval alink As Link)_ 27 As
Link 28
Get 29 Return alink.NextLink() 30 End
Get 31 End
Property 32 Public Readonly
Property Getcurrentlink()As Link 33
Get 34
Return m_currentlink 35 End
Get 36 End
Property 37
Public Readonly Property Getfirstunkoas Link 38
Get 39
Return m_firstlink 40 End
Get 41 End
Property 42 43 '
Вкладений клас для посилань 44
Friend Class Link 45
Private m_mydata As String 46
Private m_nextlink As Link 47 Friend Sub
New(Byval myparent As Link.ByVal thedata As String) 48
m_mydata - thedata 49 myparent.m_NextLink
= Me 50 ' End
Sub 51 Friend Sub
New(Byval thedata As String) 52
m_mydata =theData 53 End
Sub 54 Friend Readonly
Property Mydata()As String 55
Get 56
Return m_mydata 57 End
Get 58 End
Property 59 Friend Readonly
Property Nextlink()As Link 60
Get 61
Return m_nextlink 62 End
Get 63 End
Property 64 End
Class 65 End
Class 66 End Module Рядок 4
створює новий екземпляр зв'язаного списку. У рядку 5 визначається об'єктна
змінна типу Link. Оскільки клас Link є вкладеним по відношенню до
Linkedlist, його тип записується у вигляді «повного імені» Linkedlist.Link.
Рядки 6-12 містять невелику тестову програму. У
рядках 17-20 визначається конструктор класу Linkedlist, в якому викликається
другий конструктор класу Link (рядки 51-53). Останній оголошений з атрибутом
Friend і тому доступний для зовнішнього класу Li nkedli st. Якби конструктор Link
був оголошений з атрибутом Private, то він став б"и недоступним для зовнішнього
класу. Також варто
звернути увагу на те, як в першому конструкторі класу Link (рядки 47-50)
організовується посилання на тільки що створений елемент списку з попереднього елементу.
Для цього використовується ключове слово Me — це дуже принциповий момент,
тому рядок 49 виділена в лістингу жирним шрифтом. На перший погляд команда
myparent.m_NextLink = Me виглядає
неприпустимою, оскільки ми звертаємося до закритого поля батьківського класу
myparent. Проте програма все-таки працює! Отже, запам'ятаєте дуже важливе правило: Для екземпляра
класу завжди доступні закриті поля інших екземплярів цього класу.
Повернемося
до класу Еmploуєе. Допустимо, кожному працівникові необхідно привласнити унікальний
номер. У старих версіях VB завдання вирішувалося за допомогою глобальних змінних,
що приводило до порушення інкапсуляції і створювало потенційну загрозу випадкової
зміни номерів зовнішнім кодом. Логіка підказує, що номер повинен збільшуватися
тільки при створенні нового об'єкту Empl оуєе. У VB .NET
нарешті з'явилися засоби для досягнення цієї мети. Ідея проста: у класі
визначаються дані, спільно використовувані всіма екземплярами даного класу,
проте зовнішній доступ до цих даних знаходиться під вашим повним контролем (наприклад,
через звернення до властивості). Не варто і говорити, що ці поля ніколи не повинні
оголошуватися відкритими... Такі поля
називаються загальними (shared). Вони ідеально підходять для таких ситуацій,
як в нашому призерові з привласненням послідовних номерів. У класах також
можуть визначатися загальні властивості і методи. Недолік полягає в тому, що
загальні члени класів не можуть працювати із звичайними полями, властивостями або методами.
Інакше кажучи, загальні члени працюють тільки з іншими загальними членами. Річ у тому,
що загальні дані існують ще до створення об'єкту, тому було б нелогічне
дозволяти загальним членам доступ до конкретних об'єктів. Нижче приведений
фрагмент нової версії класу Employee з використанням загальних даних для привласнення
номерів. У класі визначається закрита загальна змінна типу Integer, яка:
В сукупності
це означає, що працівникові ніколи не буде привласнений номер 0 і що новий номер
виділяється тільки при створенні нового об'єкту Empl oyee — саме це нам і потрібне: Public
Class Employee Private
m_name As String Private
m_salary As Decimal Private
Shared m_employeeid As Integer = 1 Public Sub New(Byval
thename As String. Byval cursalary As Decimal) m_name =
thename m_salary
= cursalary m_employeeid = m_employeeid + 1 End Sub Readonly Property Employeeld()
As Integer Get Employeeld = m_employeeid End Get End Property End Class Нижче приведена
невелика програма для тестування класу Empl oyee, а також повний код класу
із загальним полем: Option
Strict On Module Modulel Sub Main() Dim Tom As New
Employee("Tom". 100000) System.Console.WriteLine(Tom.TheName
& "is employee! " & _ Tom. Employee ID & "with salary " & Tom.SalaryO) Dim Sally As New Employee("Sally". 150000) System.Console.WriteLine(Sally.TheName
& "is employee!" & _ Sally.EmployeeID &"with salary "Ssally.Salary()) System.Console.WriteLine("Please press the Enter key") System.Console.Read() End Sub End
Module Public
Class Employee Private
m_name As 'String Private
m_salary As Decimal Private
Shared m_employeeid As Integer = 1 Public Sub New(Byval
thename As String.ByVal cursalary As Decimal) m_name =
thename m_salary
= cursalary m_employeeid = m_employeeid + 1 End Sub Readonly
Property Employeeld()As Integer Get Employeeld = m_employeeid End Get End Property Readonly Property Thename()
As String Get Thename
= m_name End Get . End Property Readonly Property Salary
() As Decimal Get Salary =
m_sa1ary End Get End Property End Class Звернення
до констант в класах не відрізняються від звернень до загальних полів, але при оголошенні
констант замість Shared використовується ключове слово Const. Звичайно, оголошення
відкритих констант не приводить до порушення інкапсуляції.
|
|
|||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||