|
|||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||
|
Як
стати начальником?
Припустимо, ви побудували чудову об'єктно-орієнтовану систему обліку кадрів,
в якій повною мірою використовуються всі переваги поліморфізма.
А зараз спробуйте відповісти на просте питання — як у вашій системі реалізований
переклад простого працівника в менеджери? Як не
дивно, в ООП подібні операції (тобто зміна типу поточного екземпляра в об'єктно-орієнтованій
програмі) вважаються за один з складних аспектів архітектури додатку,
про який зазвичай ніхто серйозно не думає, поки ситуація не стане
критичною. Відповідно до специфіки об'єктно-орієнтованого програмування після
створення об'єкту змінити його тип неможливо. У нашій системі
обліку кадрів існує тільки одне прийнятне рішення — включити в клас Employee
метод, який копіює стан Employee в новий об'єкт Manager, після чого
позначає старий об'єкт Employee як невживаний.
Проглядання
ієрархії спадкоємства
З ускладненням
ієрархії класів в програмі на допомогу приходить вікно класів і Object Browser.
Наприклад, з вікна класів на мал. 5.1 видно, що клас Programmer є похідним
від класу Employee і перевизначає тільки конструктор і метод Raisesalary.
Мал. 5.1. Ієрархія спадкоємства у вікні класів
Правила
перетворення і звернення до членів класів в ієрархії спадкоємства
Об'єкти похідних
класів можуть зберігатися в змінних базових класів: Dim tom As New
Programmer("Tom". 65000) Dim
employeeofthemonth As Employee employeeofthemonth = torn У режимі
жорсткої перевірки типів (Option Strict On), якщо об'єкт tom зберігається в змінній
employeeofthemonth, для збереження його в змінній Programmer доводиться використовувати
функцію Стуре, оскільки компілятор заздалегідь не знає, що таке перетворення
можливе: Dim
programrneroncall As Programmer programmeroncal1 =
Ctype(employeeofthemonth,programmer) Звичайно,
просте збереження tom в змінній programmeroncall виконується простим привласненням.
Спадкоємство
часто допомагає позбавитися від громіздких конструкцій Select Case і If-then-else,
щоб вся чорнова робота виконувалася компілятором і механізмом поліморфізму.
Наприклад, цикл з наступного фрагмента працює як з екземплярами класу Employee,
так і з екземплярами Programmer: Sub Maln() Dim tom As New Employee("Tom". 50000) Dim sally As New Programmer("Sally", 150000) Dim ouremployees(l) As Employee ourempl.oyees(0)=tom ouremployees(l)= Sally Dim anemployee As Employee For Each anemployee In ouremployees anemployee.RaiseSalary(0.1d) Console.WriteLine(anemployee.TheName
& "salary now is " & _ anemployee.Salary())
Next Console. Readline() End Sub Результат виконання
цього прикладу показаний на мал. 5.2. Ми бачимо, що в кожному випадку викликається
правильний метод Raisesalary, не дивлячись на те що в масиві типу Employee
зберігаються як об'єкти Employee, так і об'єкти Programmers.
Мал.
5.2. Використання
поліморфізму в програмі
Public Class Programmer Inherits Employee Private m_gadget As String Public Sub New(Byval thename As String. Byval cursalary
As Decimal) End Sub Public Overloads
Overrides Sub Raisesalary(Byval Percent As Decimal) End
Sub End Set End Property End Class
Console.WriteLine(ouremployeesd).TheName
& "gadget is an "_ End Sub При спробі
відкомпілювати новий варіант програми буде видано повідомлення про помилку: C:\book to comp\chapter
5\virtualproblems\virtualproblems\modulel.vb(17): The name 'Gadget'is not а
member of 'Virtualproblems.Employee1. Хоча об'єкт sally,
що зберігається в елементі масиву ouremployees(l), відноситься до типу Programmer,
компілятор цей не знає і тому не може знайти властивість Computergadget. Більш
того, при включеному режимі Option Strict (а відключати його не рекомендується)
для використання унікальних членів класу Programmer вам доведеться проводити
явне перетворення елементів масиву до типу Programmer: Console.WriteLine(ouremployees(l).TheName & "gadget is an " & _ Ctype(ouremployeesd),
Programmer).ComputerGadget) Перетворення об'єкту, що зберігається в
об'єктній змінній базового типу, в об'єкт похідного класу називається знижуючим
перетворенням (down-casting); зворотне перетворення називається таким,
що підвищує (upcasting). Знижуюче перетворення вельми
широко поширене, проте використовувати його не рекомендується, оскільки при
цьому часто доводиться перевіряти фактичний тип об'єктної змінної в конструкціях наступного
вигляду: If Typeof ouremployees(l) Is Programmer Then Else If Typeof ouremployees(l)
Is Employee Then End If Перед вами ті
самі конструкції, для боротьби з якими нам знадобився поліморфізм! (Перетворення, що
підвищує, завжди обходиться без проблем, оскільки основоположне правило
спадкоємства свідчить, що об'єкти похідних класів завжди можуть використовуватися
замість об'єктів базових класів.)
Термін «заміщення» (shadowing)
зустрічався і в ранніх версіях VB, і в більшості мов програмування. Локальна
змінна, ім'я якої збігається з ім'ям змінної, ширшою зоною
видимості, що володіє, заміщає (приховує) цю змінну. До
речі, це одна з причин, по якій змінним рівня модуля зазвичай привласнюються
префікси m_, а глобальні змінні забезпечуються префіксами g_ — грамотний вибір
імен допомагає уникнути помилок заміщення. Перевизначення успадкованого методу
теж можна розглядати як свого роду заміщення. У VB .NET підтримується
ще одна, надзвичайно могутній різновид заміщення: Член похідного
класу, помічений ключовим словом Shadows (яке вперше з'явилося в бета-версиі
2), заміщає всі однойменні члени базового класу. За
допомогою ключового слова Shadows можна визначити в похідному класі функцію, ім'я
якої збігається з ім'ям процедури базового класу. З практичної точки
зору ключове слово Shadows приводить до того, що в похідному класі
з'являється абсолютно новий член із заданим ім'ям, внаслідок чого всі однойменні
успадковані члени стають недоступними в похідному класі. З цього
виходить, що успадковані члени класу з ключовим словом Shadows неможливо
перевизначити, тому поліморфізм перестає працювати. Іноді заміщення
ускладнює ситуацію і приводить до виникнення нетривіальних помилок — наприклад,
при поліморфному виклику заміщених методів і властивостей через об'єкт базового класу.
Щоб розглянути ці проблеми на конкретному прикладі, ми внесемо деякі зміни
до класу Programmer (нові рядки виділені жирним шрифтом): Public Class Programmer Inherits Employee Private m_gadget As String Private m_howtocallme As String = "Code guru " Public Sub Newcbyval
thename As String, Byval cursalary As Decimal) Mybase.New(thename,
cursalary) m_howtocal1me = m_howtocallme Sthename End Sub Public Overloads
Overrides Sub Raisesalary(Byval Percent As Decimal) Mybase.RaiseSalary(1.2d * Percent, "special") End Sub Public Shadows
Readonly Property Thename() As String Get Return
mjtowtocallme End Get End Property End
Class А зараз
спробуйте запустити новий варіант процедури Sub Main: Sub Main() Dim torn As
New Employee('Tom". 50000) Dim sally As
New Programmer("Sally". 150000) Console.WriteLinetsally.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.WriteLinetanEmployee.TheName & "salary now is " & anemployee. Salary()) Next Console. Readline() End Sub
Мал.
5.3. Заміщення
порушує роботу поліморфних викликів Результат
показаний на мал. 5.3. Як
видно з малюнка, поліморфний виклик перестав працювати. Перший рядок, виділений
в Sub Main жирним шрифтом, правильно ставить перед ім'ям Sally титул
«Code Guru». На жаль, в другому виділеному рядку поліморфізм вже не
працює, унаслідок чого не викликається метод Thename похідного класу
Programmer. Результат — ім'я виводиться без титулу. Іншими словами, при використанні
ключового слова Shadows звернення до членів об'єктів здійснюються відповідно до типу
контейнера, в якому зберігається об'єкт, а не їх фактичного типу (можна сказати,
що при використанні ключового слова Shadows в похідному класі метод або
властивість стає невіртуальним).
|
|
|||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||