![]() |
![]() |
![]() |
Интерфейсы
Вероятно,
вы убедились в том, что наследование занимает важное место в VB .NET, но для
полноценного использования объектно-ориентированных средств VB .NET вам также
придется освоить реализацию интерфейсов. Этой важной теме посвящены несколько
ближайших разделов.
Прежде всего
реализацию интерфейса можно рассматривать как контракт, обязательный
для любого класса. Интерфейсы занимали важнейшее место в программировании СОМ,
а также в реализации объектно-ориентированных средств в прежних версиях VB.
При реализации интерфейса класс-обязуется предоставлять некоторую функциональность
в соответствии с сигнатурами заголовков членов, отныне
и во веки веков. В отличие от объектов при наследовании интерфейсы не связаны
никакими взаимными зависимостями — каждая реализация интерфейса существует независимо
от других.
В
мире ООП часто приходится слышать высказывания типа «композиция предпочтитель-нее
наследования» (то есть лучше использовать интерфейсы, а не наследование).
Благодаря средствам контроля версии в .NET выбор между наследованием и композицией
уже не играет столь принципиальной роли. Используйте наследование всюду, где
это уместно, — там, где существует ярко выраженная связь типа «является
частным случаем».
Реализация
интерфейса предполагает, что ваш класс содержит методы со строго определенными
сигнатурами. Эти методы могут быть пустыми, но они обязательно должны
присутствовать.
Фактическая
реализация методов не фиксируется; как было только что сказано, методы могут
вообще ничего не делать. Поддержка интерфейса — всего лишь обязательство определить
методы с заданными сигнатурами. Из этого простого факта вытекает множество замечательных
следствий. Особый интерес представляют следующие:
При
вызове метода, реализованного в составе интерфейса, компилятор .NET еще на стадии
компиляции может вычислить вызываемый метод на основании сигнатуры и типа класса
(это называется ранним связыванием). Этот факт объясняет возможность использования
полиморфизма при реализации интерфейсов.
А теперь
подумайте, что произойдет, если:
Происходит
следующее: в режиме жесткой проверки типов (Option StrictOn) программа вообще
не будет компилироваться. Если этот режим отключить, умный компилятор .NET поймет,
что вызов метода класса не удастся заменить в откомпилированном коде неким подобием
простого вызова функции. Таким образом, компилятору придется сгенерировать значительно
больший объем кода. Фактически он должен во время выполнения программы вежливо
спросить у объекта, поддерживает ли он метод с указанной сигнатурой, и если
поддерживает — не будет ли он возражать против его вызова? Подобное решение
обладает двумя характерными особенностями, из-за которых оно работает значительно
медленнее и гораздо чаще приводит к возникновению ошибок:
- Необходимо предусмотреть
обработку ошибок на случай непредвиденных ситуаций.
- Поскольку компилятор
на стадии компиляции не может определить, по какому адресу следует передать
управление в блоке памяти, занимаемом объектом, ему приходится полагаться
на косвенные методы передачи управления на стадии выполнения.
Описанный
процесс называется поздним связыванием (late binding). Он не только значительно
уступает раннему связыванию по скорости, но и вообще не разрешен при включенном
режиме Option Strict за исключением позднего связывания, основанного на применении
рефлексии.
Механика
реализации интерфейса
Во многих
компаниях, занимающихся программированием (хотя бы в Microsoft), существует
должность ведущего программиста или ведущего специалиста по тестированию. Предположим,
вы решили расширить систему учета кадров и включить в нее эти новые должности
с особыми свойствами — скажем, наличием фонда материального поощрения для особо
отличившихся подчиненных.
В описанной
выше иерархии классов VB .NET определить новый класс «ведущий специалист»
не удастся, поскольку классы Programmer и Tester уже являются производными от
класса Empl oyee, а множественное наследование в .NET не поддерживается. Перед
нами идеальный пример ситуации, когда вам на помощь приходят интерфейсы.
По
общепринятым правилам имена интерфейсов в .NET начинаются с прописной бук-вы
«I», поэтому в следующем примере интерфейс называется ILead.
Прежде всего
интерфейс необходимо определить. В отличие от VB6, где интерфейс был обычным
классом, в VB .NET появилось специальное ключевое слово Interface. Предположим,
наши «ведущие» должны оценивать своих подчиненных и тратить средства
из фонда материального поощрения. Определение интерфейса выглядит так:
Public Interface
ILead
Sub SpendMoraleFund(ByVal
amount As Decimal)
Function Rate(ByVal
aPerson As Employee) As String
Property MyTeam()
As Empl oyee ()
Property MoraleFuod()
As Decimal End Interface
Обратите
внимание — в определении интерфейса отсутствуют модификаторы уровня доступа
Publiс и Private. Разрешены только объявления Sub, Function и Property с ключевыми
словами Overloads и Default. Как видите, определение интерфейса выглядит просто.
Любой класс, реализующий интерфейс ILead, обязуется содержать:
Как будет
показано ниже, имена методов реализации несущественны — главное, чтобы методы
имели заданную сигнатуру.
Чтобы реализовать
интерфейс в классе, прежде всего убедитесь в том, что он сам или ссылка на него
входит в проект. Далее за именем класса и командой Inherits в программу включается
строка с ключевым словом Implements, за которым следует имя интерфейса. Пример:
Public Class
LeadProgrammer
Inherits Programmer
Implements Head
End Class
Имя Head
подчеркивается синей волнистой чертой, свидетельствующей о возникшей проблеме.
Тем самым компилятор настаивает на выполнении обязательств по реализации интерфейса
хотя бы пустыми методами.
Как это сделать?
В отличие от ранних версий VB, где члены классов, входящие в реализацию интерфейса,
обозначались особой формой сигнатуры, в VB .NET используется более наглядный
синтаксис. В следующем фрагменте соответствующая строка выделена жирным шрифтом.
Public Function
Rate(ByVal aPerson As Employee) As String _
Implements ILead.Rate
End Function
Конечно,
имена членов интерфейса обычно совпадают с именами методов, их реализующих,
но это не обязательно. Например, следующий фрагмент вполне допустим.
Public Property OurMoraleFund() As Decimal Implements
Head.MoraleFund
Get
Return m_Moral e Fund
End Get
Set(ByVal Value
As Decimal)
m_MoraleFund =Value
End Set
End Property
Главное,
чтобы типы параметров и возвращаемого значения соответствовали сигнатуре данного
члена интерфейса. При объявлении метода могут использоваться любые допустимые
модификаторы, не мешающие выполнению контракта, — Overloads, Overrides, Overridable,
Public, Private, Protected, Friend, Protected Friend, MustOverride, Default
и Static. Запрещается только использовать атрибут Shared, поскольку члены интерфейса
должны принадлежать конкретному экземпляру, а не классу в целом.
Если в реализации
интерфейса используется член класса с модификатором Pri vate, обращения к этому
члену возможны только через переменную, объявленную с типом данного интерфейса.
В отличие от предыдущих версий VB в остальных случаях к членам интерфейса всегда
можно обращаться через объекты класса. Теперь вам не придется присваивать их
промежуточным интерфейсным переменным. Пример:
Dim tom As New
LeadProgrammer("Tom",65000) tom.SpendMoraleFund(500)
Однако в обратных
преобразованиях приходится использовать функцию СТуре:
Dim tom As New
LeadProgrammer("Tom". 65000)
Dim aLead As
ILead.aName As String
aLead = tom
aName = Ctype(aLead.
Programmer).TheName 'OK
Следующая строка
недопустима:
aName =tom.TheName
' ЗАПРЕЩЕНО!
Ниже перечислены
общие правила преобразования между типом объекта и интерфейсом, им реализуемым.
Чтобы определить,
реализует ли объект некоторый интерфейс, воспользуйтесь ключевым словом TypeOf
в сочетании с Is. Пример:
Dim torn As New LeadProgrammer("tom". 50000)
Console.WriteLine((TypeOf
(tom) Is Head))
Вторая строка
выводит значение True.
Один метод
может реализовывать несколько функций, определенных в одном интерфейсе:
Public Sub itsOK Implements
Interface1.Ml.Interfacel.M2,Interfacel.M3
Ниже приведена
полная версия класса LeadProgrammer. Конечно, реализация методов интерфейса
выглядит несколько условно, однако опадает представление о том, что можно сделать
при реализации интерфейса:
Public Class LeadProgrammer
Inherits Programmer Implements Head
Private m_MoraleFund As Decimal
Private m_MyTeam
As Employee()
Public Function Rate(ByVal aPerson As Employee) As String _
Implements Head.Rate
Return aPerson.TheName & "rating to be done"
End Function
Public Property MyTeam() As Employee()
Implements ILead.MyTeam
Get
Return m_MyTeam
End Get
SeUByVal Value As Employee()) X.
m_MyTeam = Value
End Set End
Property
Public Sub SpendMoraleFund(ByVal
amount As Decimal)_
Implements ILead.SpendMocaleFund
' Израсходовать
средства из фонда мат. поощрения
Console.WriteLine("Spent " & amount.ToString())
End Sub
Public Property OurMoraleFund()As Decimal
Implements ILead.MoraleFund
Get
Return m_MoraleFund
End Get
SettByVal Value
As Decimal)
m_MoraleFund
= Value
End Set End Property
Public Sub New(ByVal
theName As String. ByVal curSalary As Decimal)
MyBase.New(theName. curSalary)
End Sub
End Class
![]() |
![]() |
![]() |