Нетривиальное применение интерфейсов
Интерфейсы также могут объявляться производными от других интерфейсов. В этом случае интерфейс просто дополняется новыми членами. Предположим, в нашей системе учета кадров ведущим программистам предоставлено право разрешать модернизацию компьютеров своих подчиненных. В программе это моделируется методом 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
- ICloneable: в классе
реализуется метод Clone, обеспечивающий глубокое копирование.
- IDisposable: класс
потребляет ресурсы, которые не могут автоматически освобождаться сборщиком
мусора.
реализация циклов For-Each в VB6, они станут для вас настоящим подарком!
Как было показано в разделе «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
Выше уже упоминалось о том, что метод Finalize не обеспечивает надежного освобождения ресурсов, не находящихся под управлением сборщика мусора. В программировании .NET у этой задачи существует общепринятое решение — класс реализует интерфейс IDisposable с единственным методом Dispose, освобождающим занятые ресурсы:
Public Interface IDisposable
Sub Dispose() End Interface
Итак, запомните следующее правило:
Если ваш класс использует другой класс, реализующий IDisposable, то в конце работы с ним необходимо вызвать метод Dispose.
Как будет показано в главе 8, метод Dispose должен вызываться в каждом графическом приложении, зависящем от базового класса Component, поскольку это необходимо для освобождения графических контекстов, используемых всеми компонентами.
Коллекцией (collection) называется объект, предназначенный для хранения других объектов. Коллекция содержит методы для включения и удаления внутренних объектов, а также обращения к ним в разных вариантах — от простейшей индексации, как при работе с массивами, до сложной выборки по ключу, как в классе Hashtable, представленном в предыдущей главе. .NET Framework содержит немало полезных классов коллекций. Расширение этих классов посредством наследования позволяет строить специализированные коллекции, безопасные по отношению к типам. И все же при нетривиальном использовании встроенных классов коллекций необходимо знать, какие интерфейсы в них реализованы. Несколько ближайших разделов посвящены стандартным интерфейсам коллекций.
For
Each и интерфейс lEnumerable
Второй способ, основанный на самостоятельной реализации интерфейса 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