Обращение к функциональности базового класса

В 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 четко показывает, что метод базового класса должен переопределяться в производном классе. Для этого используются два специальных ключевых слова.

  • Ключевое слово Overridable указывается в базовом классе для методов, которые могут переопределяться производными классами.
  • Ключевое слово Overrides указывается в производном классе для переопределяемых методов.
  • Естественно, типы параметров и возвращаемого значения должны совпадать. Если они различаются, происходит не переопределение, а перегрузка.

    Ниже приведен примерный вид базового класса 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

    Подведем итог:

  • Переопределение допускается только для членов базовых классов, объявленных с ключевым словом Overridable.
  • Если на некоторой стадии построения иерархии классов потребуется запретить дальнейшее переопределение метода в производных классах, этот метод помечается ключевым словом NotOverridable.
  • Ключевое слово 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 не позволяет получить доступ к «дедушкам» и «прадедушкам» класса в иерархии наследования; запись вида MyBase.MyBase. MemberFunction недопустима.
  • MyBase является ключевым словом языка. В отличие от Me, MyBase нельзя использовать с оператором Is, присваивать объектным переменным или передавать процедурам в качестве параметра.
  • С MyBase тесно связано другое ключевое слово — MyClass. Оно гарантирует, что даже в случае переопределения будет вызван метод, определенный в текущем классе, а не какая-то из его переопределенных версий в производных классах. На ключевое слово MyCl ass распространяются те же ограничения, что и на ключевое слово MyBase, о котором упоминалось в предыдущей главе.

  • MyBase — ключевое слово, а не реальный объект. Следовательно, MyClass, как и MyBase, нельзя использовать с оператором Is, присваивать объектным переменным или передавать процедурам в качестве параметра (для ссылки на конкретный экземпляр используется ключевое слово Me).
  • Ключевое слово MyClass не позволяет получить доступ к закрытым (Private) членам класса, тогда как Me предоставляет такую возможность.
  • На практике ключевое слово MyClass приносит наибольшую пользу в тех случаях, ког-да мы хотим указать на модификацию поведения класса. Замена его на Me не дает нужного эффекта, поскольку ключевое слово Me означает «текущий экземпляр, код которого выполняется в настоящий момент», и попытки применения его в другом контексте лишь сбивают с толку.