Обращение
к функциональности базового класса
В VB .NET
существует модификатор Protected, который автоматически предоставляет доступ
из производных классов к соответствующему члену класса, будь то метод или переменная
класса. Возникает искушение воспользоваться этим модификатором и объявить все
поля базового класса с уровнем доступа Protected, чтобы производные классы могли
легко и быстро работать с ними. Не поддавайтесь соблазну! Хороший стиль проектирования
требует, чтобы модификатор Protected использовался только для методов, но не
для переменных. В противном случае нарушается инкапсуляция и теряется возможность
проверки данных там, где она должна происходить — в базовом классе. Как и в
реальной жизни, здесь действует хороший принцип «доверяй, но проверяй».
Например,
в исходное определение класса Employee входят свойства со следующими сигнатурами:
Public Readonly Property TheName() As String
Public Readonly
Property Salary() As Decimal
Таким образом,
доступ к этим свойствам разрешен всем классам. Чтобы ограничить доступ к свойствам
классами, производными от Empl oyee, замените модификатор Publ ic на Protected.
В табл. 5.1
перечислены различные модификаторы уровня доступа, присваиваемые членам классов
в иерархии наследования.
Как было
сказано выше, функции (но не поля!) с модификаторами Protected и Protected Friend
распространены достаточно широко, поскольку они предотвращают доступ к защищенным
членам со стороны внешнего кода.
При использовании
Protected возникает весьма интересный подвох. К счастью, компилятор вовремя
предупредит вас о возможных проблемах. Рассмотрим конкретный пример: допустим,
у вас имеется класс GeekFest с методом Boast, который пытается обратиться к
свойству Salary класса Programmer (что в конечном счете означает доступ к свойству
Sal агу базового класса Empl oyee). Ниже приведен примерный вид программы:
Public Class
GeekFest
Private m_Programmers()
As Programmer
Sub New(ByVal
Programmers() As Programmer)
m_Programmers = Programmers
End Sub
Public Function
Boast(ByVal aGeek As Programmer) As String
Return "Hey my salary is " & aGeek.Salary
End Function
End Class
"left"> Таблица
5.1. Модификаторы уровня доступа при наследовании
Модификатор |
Описание |
||
Friend | Доступ предоставляется только из текущей сборки | ||
Private | Доступ предоставляется только объектам базового класса | ||
Protected | Доступ ограничивается объектами базового класса и объектами любых производных классов | ||
Protected Friend | Доступ предоставляется только из текущей сборки или из классов, производных отданного базового класса (может рассматриваться как комбинация модификаторов Protected и Friend) | ||
Public | Доступ к члену класса предоставляется всем, кто имеет доступ к классу в соответствии с модификатором самого класса | ||
Также допустим,
что в класс Empl oyee входит свойство Sal агу, доступное только для чтения и
помеченное модификатором Protected вместо Public:
Protected Readonly
Property Salary() As Decimal
Get
Return MyClass.m_Salary
End Get End
Property
В результате
компилятор выдает сообщение об ошибке:
C:\vb net book\chapter 5\Examplel\Examplel\Moduleld.vb(19):
'Examplel. Modulel.Employee. Protected Readonly Property Salary()
As Decimal'
is Protected.and is not accessible in this context.
Хотя класс
Programmer обладает доступом к защищенному свойству Salary в своем коде,
объекты Programmer не имеют доступа к этому методу за пределами кода
класса Programmer. Подведем итог:
Обращение
к Protected-методам базового класса возможно только из объектов производного
класса, но не из внешних ссылок на эти объекты за пределами производного класса.
Переопределение
свойств и методов
В нашем примере,
где программист автоматически получает 6-процентное повышение зарплаты вместо
5-процентного, необходимо изменить поведение метода RaiseSalary и отразить в
нем автоматическую надбавку. Это называется переопределением функции.
Общие
члены классов переопределяться не могут.
В отличие
от многих объектно-ориентированных языков синтаксис VB .NET четко показывает,
что метод базового класса должен переопределяться в производном классе. Для
этого используются два специальных ключевых слова.
Естественно,
типы параметров и возвращаемого значения должны совпадать. Если они различаются,
происходит не переопределение, а перегрузка.
Ниже приведен
примерный вид базового класса Employee с методом RaiseSalary, который может
переопределяться в производных классах Programmer, Manager и т. д. Ключевые
строки кода выделены жирным шрифтом:
Option Strict
On Public Class Employee
Private m_Name
As String
Private m_Salary
As Decimal
Private Const
LIMIT As Decimal = 0.1D
Public Sub New(ByVal
theName As String,ByVal curSalary As Decimal)
m_Name =theName
m_Salary =curSalary
End Sub
Public Readonly
Property TheName()As String
Get
Return m_Name
End Get End
Property
Public 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.WriteLine('NEED
PASSWORD TO RAISE SALARY MORE " & _
"THAN LIMIT!!!!")
Else
m_Salary =(1 + 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 =(1
+ Percent) * m_Salary
End If
End Sub
End Class
Необязательное
ключевое слово Overloads, упоминавшееся в главе 4, указывает на то, что в классе
определены несколько версий RaiseSalary.
Класс
Employee часто встречается в примерах этой главы. Либо введите его в Visual
Studio, либо скачайте исходный текст с сайта www.piter.com, если вы еще не сделали
этого ранее.
В нашей модели
зарплата программиста повышается вызовом специализированной версии метода RaiseSalary.
Производный класс Programmer приведен ниже (как обычно, ключевые строки выделены
жирным шрифтом):
Public Class
Programmer
Inherits Employee
Public Sub New(ByVal
theName As String, ByVal curSalary As Decimal)
MyBase.New(theName, curSalary)
End Sub
Public Overloads
Overrides Sub RaiseSalaryCByVal Percent As Decimal)
MyBase.RaiseSalary(1.2D *Percent."special")
End Sub
End Class
Обратите
внимание, каким компактным получился производный класс — большая часть функциональности
осталась неизменной, поэтому мы просто наследуем ее от базового класса!
В приведенной
ниже процедуре Sub Main компилятор генерирует вызов правильной версии метода
Rai seSal ary (с 20-процентной надбавкой) для объекта sal 1у, относящегося к
классу Programmer:
Sub Main()
Dim sally As New Programmed"Sally". 150000D) sally.RaiseSalary(0.1D)
' С учетом надбавки для программистов
Console.WriteLine(sally.TheName & " salary is now " & sally.Salary())
Console.ReadLine()
End Sub
Подведем
итог:
Ключевое
слово VB .NET Notlnheritable полностью запрещает наследование от класса. Как
правило, наследование запрещается для классов, выполняющих очень важные функции,
которые ни в коем случае, на должны изменяться. Многие классы .NET Framework
(такие, как String) помечены ключевым словом Notlnheritable именно по этой причине.
Впрочем, если требуется запретить переопределение лишь одного члена класса,
незачем запрещать наследование для всего класса; достаточно пометить ключевым
словом NotOverridable нужный член класса.
По
умолчанию переопределение членов классов запрещается (см. описание ключевого
слова Shadows ниже в этой главе). И все же ключевое слово NotOverridable рекомендуется
использовать, поскольку оно более наглядно выражает намерения программиста.
Иногда при
переопределении метода или свойства возникает необходимость вызвать версию базового
класса. Допустим, имени каждого программиста в классе Programmer должен предшествовать
почетный титул «Code Guru». Ключевое слово MyBase позволяет обратиться
к открытому свойству TheName базового класса в производном классе:
Public Overrides
Readonly Property TheName() As String
Get
Return "Code
Guru " & MyBase.TheName()
End Get
End Property
Учтите, что
ключевое слово MyBase обладает рядом ограничений:
С MyBase
тесно связано другое ключевое слово — MyClass. Оно гарантирует, что даже в случае
переопределения будет вызван метод, определенный в текущем классе, а не какая-то
из его переопределенных версий в производных классах. На ключевое слово MyCl
ass распространяются те же ограничения, что и на ключевое слово MyBase, о котором
упоминалось в предыдущей главе.
На
практике ключевое слово MyClass приносит наибольшую пользу в тех случаях, ког-да
мы хотим указать на модификацию поведения класса. Замена его на Me не дает нужного
эффекта, поскольку ключевое слово Me означает «текущий экземпляр, код
которого выполняется в настоящий момент», и попытки применения его в другом
контексте лишь сбивают с толку.