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

         

Нетривиальное применение интерфейсов


Интерфейсы также могут объявляться производными от других интерфейсов. В этом случае интерфейс

просто дополняется новыми членами. Предположим, в нашей системе учета кадров ведущим программистам предоставлено право разрешать модернизацию компьютеров своих подчиненных. В программе это моделируется методом UpGradeHardware:

Public Interface ILeadProgrammer

Inherits Head

Public Function UpGradeHardware(aPerson As Programmer)

End Interface

В этом случае реализация ILeadProgrammer требует дополнительного выполнения контракта интерфейса Head.

В отличие от классов, которые могут наследовать лишь от одного базового класса, интерфейс может быть объявлен производным от нескольких интерфейсов:

Public Interface ILeadProgrammer

Inherits Head.Inherits ICodeGuru

Public Function UpGradeHardware(aPerson As Programmer)

End Interface

Поскольку интерфейс может наследовать от нескольких интерфейсов, реальна ситуация, при которой в нем потребуется определить два одноименных метода, принадлежащих к разным интерфейсам, — например, если интерфейсы Head и ICodeGuru содержат методы с именем SpendMoraleFund. В этом случае вы не сможете обратиться к одному из этих методов через переменную типа, реализующего такой интерфейс:

Dim tom As New LeadProgrammer("Tom", 65000)

tom.SpendMoraleFund(500)

Интерфейс должен указываться явно, как в следующем фрагменте:

Dim tom As New LeadProgrammer("Tom", 65000)

Dim aCodeGuru As ICodeGuru

aCodeGuru = tom

aCodeGuru.SpendMoraleFund(500)

 

Выбор между интерфейсами и наследованием

Хотя на первый взгляд интерфейсы чем-то напоминают базовые классы, от этой аналогии больше вреда, чем пользы. Абстрактный класс может содержать реализованные методы, а в интерфейсе они недопустимы. Абстрактные базовые классы создаются только в результате тщательного анализа функциональности с выделением самого примитивного общего предка, и ни по какой другой причине.

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

 

Важнейшие интерфейсы .NET Framework

Описать все интерфейсы .NET Framework на нескольких страницах невозможно, но хотя бы получить некоторое представление о них вполне реально. Интерфейсы ICloneable и IDisposable обладают особой декларативной функцией — реализуя их, вы тем самым заявляете, что ваш класс обладает некой стандартной функциональностью, присутствующей во многих классах.

  • ICloneable: в классе реализуется метод Clone, обеспечивающий глубокое копирование.
  • IDisposable: класс потребляет ресурсы, которые не могут автоматически освобождаться сборщиком мусора.
Далее в этой главе рассматриваются базовые интерфейсы для построения специализированных коллекций. Если вы помните, с какими трудностями была связана

реализация циклов For-Each в VB6, они станут для вас настоящим подарком!

 

ICloneable

Как было показано в разделе «MemberWiseClone», клонирование объекта, содержащего внутренние объекты, вызывает немало проблем. Разработчики .NET дают вам возможность сообщить о том, что данная возможность реализована в вашем классе. Для этой цели используется декларативный интерфейс ICloneable, состоящий из единственной функции Clone:

Public Interface ICloneable

Function Clone() As Object

End Interface

Этот интерфейс (а следовательно, и метод Clone) реализуется в том случае, если вы хотите предоставить пользователям своего класса средства для клонирования экземпляров. Далее вы сами выбираете фактическую реализацию метода Clone — не исключено, что она будет сводиться к простому вызову MemberWiseClone. Как было сказано выше, MemberWiseCl one нормально клонирует экземпляры, поля которых относятся к структурному типу или являются неизменяемыми (такие, как String). Например, в классе Empl oyee клонирование экземпляров может осуществляться методом Clone, поскольку все поля представляют собой либо строки, либо значения структурных типов. Таким образом, реализация IC1 опеаЫ е,для класса Empl oyee может выглядеть так:

Public Class Employee Implements ICloneable

Public Function Clone() As Object _

Implements ICloneable.Clone

Return CType(Me.MemberwiseClone, Employee)

End Function ' И т.д.

End Class

В классах, содержащих внутренние объекты, реализация метода Clone потребует значительно больших усилий (хотя в главе 9 описан прием, позволяющий достаточно просто решить эту задачу в большинстве случаев). Так, в приведенном выше классе EmbeddedObject необходимо клонировать внутренний массив, не ограничиваясь простым копированием.

Как это сделать? Очень просто. Поскольку класс Array реализует интерфейс ICloneable, он должен содержать метод для клонирования массивов. Остается лишь вызвать этот метод в нужном месте. Ниже приведена версия класса Ет-beddedObjects с реализацией ICloneabl e (ключевые строки выделены жирным шрифтом):

Public Class EmbeddedObjects Implements

ICloneable Private m_Ma() As String

Public Sub New(ByVal anArray() As String)

m_Data = anArray

End Sub

Public Function Clone() As Object Implements

ICloneable.Clone

Dim temp()As String

temp = m_Data.Clone ' Клонировать массив

Return New EmbeddedObjects(temp)

End Function

Public Sub DisplayData()

Dim temp As String

For Each temp In m_Data

Console.WriteLine(temp)

Next End

Sub Public

Sub ChangeDataCByVal

newData As String)

m_Data(0) = newData

End Sub

End Class


 

IDisposable

Выше уже упоминалось о том, что метод Finalize не обеспечивает надежного освобождения ресурсов, не находящихся под управлением сборщика мусора. В программировании .NET у этой задачи существует общепринятое решение — класс реализует интерфейс IDisposable с единственным методом Dispose, освобождающим занятые ресурсы:

Public Interface IDisposable

Sub Dispose()

End Interface

Итак, запомните следующее правило:

Если ваш класс использует другой класс, реализующий IDisposable, то в конце работы с ним необходимо вызвать метод Dispose.

Как будет показано в главе 8, метод Dispose должен вызываться в каждом графическом приложении, зависящем от базового класса Component, поскольку это необходимо для освобождения графических контекстов, используемых всеми компонентами.


 

Коллекции

Коллекцией (collection) называется объект, предназначенный для хранения других объектов. Коллекция содержит методы для включения и удаления внутренних объектов, а также обращения к ним в разных вариантах — от простейшей индексации, как при работе с массивами, до сложной выборки по ключу, как в классе Hashtable, представленном в предыдущей главе. .NET Framework содержит немало полезных классов коллекций. Расширение этих классов посредством наследования позволяет строить специализированные коллекции, безопасные по отношению к типам. И все же при нетривиальном использовании встроенных классов коллекций необходимо знать, какие интерфейсы в них реализованы. Несколько ближайших разделов посвящены стандартным интерфейсам коллекций.

 

For Each и интерфейс lEnumerable

Поддержка For-Each в классах VB6 была недостаточно интуитивной, а ее синтаксис воспринимался как нечто совершенно инородное (мы упоминали об этом в главе 1). В VB .NET существуют два способа организации поддержки For-Each в классах коллекций. Первый метод уже был продемонстрирован выше: новый класс определяется производным от класса с поддержкой For-Each и автоматически наследует его функциональность. В частности, этот способ применялся для класса Empl oyees, производного от класса System. Collections. CollectionBase.

Второй способ, основанный на самостоятельной реализации интерфейса IEnumerable, обеспечивает максимальную гибкость. Определение интерфейса выглядит следующим образом:

Public Interface lEnumerable

Function GetEnumerator() As Enumerator

End Interface

При реализации lEnumerable класс реализует метод GetEnumerator, который возвращает объект IEnumerator, обеспечивающий возможность перебора в классе. Метод перехода к следующему элементу коллекции определяется именно в интерфейсе IEnumerator, который определяется следующим образом:

Public Interface lEnumerator

Readonly Property Current As Object

Function MoveNext() As Boolean

Sub Reset ()

End Interface

В цикле For-Each перебор ведется только в одном направлении, а элементы доступны только для чтения. Этот принцип абстрагирован в интерфейсе lEnumerator — в интерфейсе присутствует метод для перехода к следующему элементу, но нет методов для изменения данных. Кроме того, в интерфейс IEnumerator должен входить обязательный метод для перехода в начало коллекции. Обычно этот интерфейс реализуется способом включения (containment): в коллекцию внедряется специальный класс, которому перепоручается выполнение трех интерфейсных методов (один из lEnumerable и два из IEnumerator).

Ниже приведен пример коллекции Employees, построенной «на пустом месте». Конечно, класс получается более сложным, чем при простом наследовании от System. Collections. CollectionBase, но зато он обладает гораздо большими возможностями. Например, вместо последовательного возвращения объектов Employee можно использовать сортировку по произвольному критерию:

1 Public Class Employees

2 Implements IEnumerable.IEnumerator

3 Private m_Employees() As Employee

4 Private m_index As Integer = -1

5 Private m_Count As Integer = 0

6 Public Function GetEnumerator() As lEnumerator _

7 Implements lEnumerable.GetEnumerator

8 Return Me

9 End Function

10 Public Readonly Property Current() As Object _

11 Implements IEnumerator.Current

12 Get

13 Return m_Employees(m_Index)

14 End Get

15 End Property

16 Public Function MoveNext() As Boolean _

17 Implements lEnumerator.MoveNext

18 If m_Index < m_Count Then

19 m_Index += 1

20 Return True

21 Else

22 Return False

23 End If

24 End Function

25 Public Sub Reset() Implements IEnumerator.Reset

26 m_Index = 0

27 End Sub

28 Public Sub New(ByVal theEmployees() As Employee)

29 If theEmployees Is Nothing Then

30 MsgBox("No items in the collection")

31 ' Инициировать исключение - см. главу 7

32 ' Throw New ApplicationException()

33 Else

34 m_Count = theEmployees.Length - 1

35 m_Employees = theEmployees

36 End If

37 End Sub

38 End Class

Строка 2 сообщает о том, что класс реализует два основных интерфейса, используемых при работе с коллекциями. Для этого необходимо реализовать функцию, которая возвращает объект lEnumerator. Как видно из строк 6-9, мы просто возвращаем текущий объект Me. Впрочем, для этого класс должен содержать реализации членов IEnumerable; они определяются в строках 10-27.


Ниже приведена небольшая тестовая программа. Предполагается, что Publiс-класс Employee входит в решение:

Sub Main()

Dim torn As New Emplpyee("Tom". 50000)

Dim sally As New Employee("Sally". 60000)

Dim joe As New Employee("Joe", 10000)

Dim theEmployees(l) As Employee

theEmployees(0) = torn

theEmployees(1) = sally

Dim myEmployees As New Employees(theEmployees)

Dim aEmployee As Employee

For Each aEmployee In myEmployees

Console.WriteLine(aEmployee.TheName)

Next

Console.ReadLine()

End Sub



Содержание раздела