Cамоучитель по VB.NET

плм меркури


 

Интерфейсы


Вероятно, вы убедились в том, что наследование занимает важное место в VB .NET, но для полноценного использования объектно-ориентированных средств VB .NET вам также придется освоить реализацию интерфейсов. Этой важной теме посвящены несколько ближайших разделов.

Прежде всего реализацию интерфейса можно рассматривать как контракт, обязательный для любого класса. Интерфейсы занимали важнейшее место в программировании СОМ, а также в реализации объектно-ориентированных средств в прежних версиях VB. При реализации интерфейса класс-обязуется предоставлять некоторую функциональность в соответствии с сигнатурами заголовков членов, отныне и во веки веков. В отличие от объектов при наследовании интерфейсы не связаны никакими взаимными зависимостями — каждая реализация интерфейса существует независимо от других.

В мире ООП часто приходится слышать высказывания типа «композиция предпочтитель-нее наследования» (то есть лучше использовать интерфейсы, а не наследование). Благодаря средствам контроля версии в .NET выбор между наследованием и композицией уже не играет столь принципиальной роли. Используйте наследование всюду, где это уместно, — там, где существует ярко выраженная связь типа «является частным случаем».

Реализация интерфейса предполагает, что ваш класс содержит методы со строго определенными сигнатурами. Эти методы могут быть пустыми, но они обязательно должны присутствовать.

Фактическая реализация методов не фиксируется; как было только что сказано, методы могут вообще ничего не делать. Поддержка интерфейса — всего лишь обязательство определить методы с заданными сигнатурами. Из этого простого факта вытекает множество замечательных следствий. Особый интерес представляют следующие:

  • «умный» компилятор может заменить вызовы функций быстрым поиском по таблице с последующей передачей управления;
  • с точки зрения программиста — разработчики могут вызывать методы вашего класса по сигнатуре, не опасаясь, что указанный метод не существует;
  • при наличии подобных обязательств компилятор может использовать полиморфизм так же, как это делается при наследовании.
При вызове метода, реализованного в составе интерфейса, компилятор .NET еще на стадии компиляции может вычислить вызываемый метод на основании сигнатуры и типа класса (это называется ранним связыванием). Этот факт объясняет возможность использования полиморфизма при реализации интерфейсов.

А теперь подумайте, что произойдет, если:

  • вы не будете связаны обязательством на поддержку метода с заданной сигнатурой в результате реализации интерфейса;
  • ваш класс не входит в иерархию наследования, в которой VB .NET сможет найти метод с нужной сигнатурой.
Происходит следующее: в режиме жесткой проверки типов (Option StrictOn) программа вообще не будет компилироваться. Если этот режим отключить, умный компилятор .NET поймет, что вызов метода класса не удастся заменить в откомпилированном коде неким подобием простого вызова функции. Таким образом, компилятору придется сгенерировать значительно больший объем кода. Фактически он должен во время выполнения программы вежливо спросить у объекта, поддерживает ли он метод с указанной сигнатурой, и если поддерживает — не будет ли он возражать против его вызова? Подобное решение обладает двумя характерными особенностями, из-за которых оно работает значительно медленнее и гораздо чаще приводит к возникновению ошибок:

  1. Необходимо предусмотреть обработку ошибок на случай непредвиденных ситуаций.
  2. Поскольку компилятор на стадии компиляции не может определить, по какому адресу следует передать управление в блоке памяти, занимаемом объектом, ему приходится полагаться на косвенные методы передачи управления на стадии выполнения.
Описанный процесс называется поздним связыванием (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, обязуется содержать:

  • процедуру с параметром типа Decimal;
  • функцию, которая получает объект Empl oyee и возвращает строку;
  • свойство, доступное для чтения и записи, возвращающее массив объектов Employee;
  • свойство, доступное для чтения и записи, возвращающее значение типа Decimа1.
Как будет показано ниже, имена методов реализации несущественны — главное, чтобы методы имели заданную сигнатуру.

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

 

Назад Начало Вперед