Семейство классов и наследование интерфейсов. Динамические классы
Обертывание коллекции VBA
Обратите внимание, наш только что приведенный пример ВводСписка, иллюстрирующий часто возникающую на практике задачу, невозможно реализовать, используя встроенный класс Collection.Причина этого уже пояснялась, - коллекция хранит ссылки на созданные вне нее элементы, она не обладает собственной памятью. Поэтому пользователь сам должен заботиться о создании и хранении элементов. Конечно, хотелось бы поручить эту задачу самой коллекции, как это делается в созданном динамическом классе СписокЛичностей. С другой стороны этот класс имеет значительно менее мощные методы AddFirst и AddLast в сравнении с методом Add класса Collection. Класс СписокЛичностей не позволяет создавать словари, он значительно менее эффективен, чем класс Collection в тех случаях, когда требуется обеспечить прямой доступ к элементам списка. Возникает вопрос, нельзя ли соединить достоинства двух подходов? Оказывается, можно. Для этого применяется стандартная при работе с классами технология обертывания. Суть ее в том, что стандартный объект, например, элемент управления встраивается в собственный класс. Методы класса могут вызывать методы объекта, сохраняя тем самым мощь стандартного объекта. С другой стороны можно добавить новые методы или расширить функциональность существующих. Мы продемонстрируем эту технологию, показав как можно строить собственные коллекции для объектов любого класса. Конечно, нам не удастся построить универсальный класс, расширяющий возможности класса Collection, но нам удастся показать, что для каждого собственного класса элементов, можно построить коллекцию, обладающую всеми свойствами обычной коллекции, но, вместе с тем, хранящую элементы в самой коллекции.
Не будем изобретать новых примеров и построим класс КоллекцияЛичностей. Этот класс будет обладать всеми стандартными свойствами и методами класса Collection, дополненными методами класса СписокЛичностей. Он будет иметь:
- Свойство Count - возвращает число элементов коллекции. Доступно только для чтения, имеет тип возвращаемого значения Long.
- Метод AddPerson (item, key, before, after) - добавляет элементы в коллекцию. Первый параметр Item является обязательным и задает добавляемый элемент. Параметр key - необязателен, он задается, когда элементу ставится в соответствие ключ. Два последних необязательных параметра уточняют позицию вставки, - задают индекс или ключ элемента, перед или после которого добавляется новый элемент. Только один из этих параметров может быть задан. Если не задан ни один, элемент добавляется в конец коллекции. Заметьте, несмотря на внешнее сходство метод AddPerson отличается от метода Add класса Collection. Во-первых, параметр Item будет иметь строго фиксированный тип Личность. Во-вторых, есть различия в реализации. Коллекция будет хранить не ссылку на элемент Item класса Личность, а копию этого элемента.
- Метод Remove(key) - удаляет элементы коллекции. Удаляется элемент с заданным ключом: заметьте, это может быть индекс элемента. После удаления происходит перенумерация элементов и уменьшается счетчик Count.
- Метод Item(key) - возвращает значение элемента списка с заданным ключом. И здесь в роли ключа может выступать обычный индекс элемента.
- Метод PrintList() - печатает элементы списка в его текущем состоянии.
- Метод PrintHystory - печатает историю создания списка, - все его элементы, даже если они и были удалены впоследствии.
Наряду с открытыми свойствами и методами наш класс будет содержать и закрытые свойства и методы:
- Свойство First - указатель на начало списка, хранящего элементы коллекции.
- Событие Initialize - конструктор по умолчанию, инициализирует список.
- Метод AddFirst - создает копии элементов и добавляет их в список, представляющий внутреннюю память коллекции.
Вот полное описание класса КоллекцияЛичностей, обертывающего класс Collection. Класс сохранил все мощные свойства стандартной коллекции и, к тому же, приобрел новые привлекательные свойства:
Option Explicit 'Определение класса КоллекцияЛичностей 'Свойства Private First As ЭлементСпискаЛичностей Private Count As Long 'Обратите внимание: встраиваем коллекцию Private Persons As Collection 'Методы Private Sub Class_Initialize() Set First = Nothing Count = 0 'при создании объекта класса КоллекцияЛичностей создается 'и внутренняя коллекция для хранения его элементов Set Persons = New Collection End Sub Private Sub AddFirst(F As Личность) Dim Elem As New ЭлементСпискаЛичностей Dim Info As New Личность 'Создаем копию переменной F. В списке будем использовать копию, а не ссылку. Info.CopyPerson F Set Elem.Сам = Info Set Elem.Друг = First Set First = Elem Count = Count + 1 End Sub Public Sub PrintList() 'Печать текущего состояния списка Dim i As Long For i = 1 To Persons.Count Persons(i).PrintPerson Next i End Sub Public Sub AddPerson(Item As Личность, Optional key As String = "", _ Optional before As Long = 0, Optional after As Long = 0) Dim P As Личность 'Вначале добавляем элемент в линейный список, 'используя внутренний метод AddFirst AddFirst Item 'Разбор случаев вызова Set P = First.Сам If key <> "" Then Persons.Add P, key ElseIf before <> 0 Then Persons.Add P,, before ElseIf after <> 0 Then Persons.Add P,,, after Else Persons.Add P End If Count = Persons.Count End Sub Public Property Get Количество() As Long Количество = Count End Property Public Sub Remove(key As Variant) Persons.Remove key Count = Persons.Count End Sub Public Function Item(key As Variant) As Личность Set Item = Persons.Item(key) End Function Public Sub PrintHystory() 'Печать всех элементов в порядке, обратном их добавлению в список. 'Печатаются все элементы, независимо от того, были ли они удалены. Dim P As ЭлементСпискаЛичностей Dim Q As Личность Set P = First While Not (P Is Nothing) Set Q = P.Сам Q.PrintPerson Set P = P.Друг Wend End Sub5.11.
Приведем комментарии к этому тексту:
- Прежде всего, следует обратить внимание на то, что объект Persons класса Collection встроен, как свойство, в создаваемый класс. Этот объект инициализируется конструктором по умолчанию, - обработчиком события Initialize в тот момент, когда создается сам объект класса КоллекцияЛичностей. Благодаря этому встроенному объекту, становятся доступны все свойства и методы стандартного класса Collection.
- Наиболее интересна реализация метода AddPerson. В этом методе вначале вызывается закрытый метод AddFirst, создающий копию добавляемого элемента и помещающий эту копию в линейный список, представляющий внутреннюю память коллекции. Следующим шагом является вызов метода Add встроенным объектом Persons. Этому предшествует разбор случаев вызова. Дело в том, что метод AddPerson, также как и его прародитель метод Add имеет возможные параметры, которые могут быть заданы или опущены в момент вызова. Так что приходится разбираться, какие из параметров были заданы при вызове.
- Функция Item и метод Remove достаточно просты в реализации, они вызывают соответствующие методы встроенного объекта.
- Свойство Count, доступное для чтения, соответствует значению этого свойства внутреннего объекта Persons.
- В интерфейс класса добавлены два метода печати элементов списка. Один из них распечатывает текущее состояние коллекции, состав элементов которой может динамически изменяться. Другой метод распечатывает историю создания коллекции. Список, представляющий внутреннюю память коллекции, сохраняет эту историю
Приведем процедуру для работы с этой коллекцией:
Public Sub Великие() 'Создание и работа с коллекцией личностей Dim Личности As New КоллекцияЛичностей Dim ЭтоЛичность As Личность 'Работа с коллекцией, как со списком Dim Адам As New Личность Адам.InitPerson "Адам", "Первый Человек", #1/1/100# Личности.AddPerson Адам, "Первый" Dim Ной As New Личность Ной.InitPerson "Ной", "Праведник", #1/1/100# Личности.AddPerson Item:=Ной, after:=1 'Работа с коллекцией, как с динамическим массивом Dim Шекспир As New Личность Шекспир.InitPerson "Вильям", "Шекспир", #4/23/1564# Личности.AddPerson Item:=Шекспир, after:=2 Dim Гомер As New Личность Гомер.InitPerson "Гомер", "Великий Слепой", #1/1/100# Личности.AddPerson Item:=Гомер, before:=3 'Работа с коллекцией, как со словарем Dim Булгаков As New Личность Булгаков.InitPerson "Михаил", "Булгаков", #1/23/1891# Личности.AddPerson Item:=Булгаков, key:="Мастер" Dim Пушкин As New Личность Пушкин.InitPerson "Александр", "Пушкин", #6/6/1799# Личности.AddPerson Item:=Пушкин, key:="Гений" 'Печать всего списка Личности.PrintList Debug.Print Личности.Количество 'Удаление элементов Личности.Remove "Первый" Личности.Remove 2 'Печать после удаления Личности.PrintList Debug.Print Личности.Количество 'Доступ к отдельным элементам по ключу Set ЭтоЛичность = Личности.Item("Гений") ЭтоЛичность.PrintPerson Set ЭтоЛичность = Личности.Item(2) ЭтоЛичность.PrintPerson End Sub5.12.
Вот результаты отладочной печати при работе этой процедуры:
Адам Первый Человек родился 01.01.100 Ной Праведник родился 01.01.100 Гомер Великий Слепой родился 01.01.100 Вильям Шекспир родился 23.04.1564 Михаил Булгаков родился 23.01.1891 Александр Пушкин родился 06.06.1799 6 Ной Праведник родился 01.01.100 Вильям Шекспир родился 23.04.1564 Михаил Булгаков родился 23.01.1891 Александр Пушкин родился 06.06.1799 4 Александр Пушкин родился 06.06.1799 Вильям Шекспир родился 23.04.1564
Приведем теперь текст процедуры, позволяющей создавать коллекцию личностей в процессе диалога с пользователем. Ранее мы отмечали, что такая процедура не может корректно работать с обычной коллекцией. С нашим же построенным классом никаких проблем нет:
Public Sub ВводКоллекции() 'Создание коллекции в диалоге с пользователем Dim Личности As New КоллекцияЛичностей Dim ЭтоЛичность As New Личность Dim Имя As String, Фамилия As String, Дата As Date If MsgBox("Начнем создавать список личностей?", vbYesNo) = vbYes Then Do ЭтоЛичность.ВашеИмя = InputBox("Введите имя личности") ЭтоЛичность.ВашаФамилия = InputBox("Введите Фамилию") ЭтоЛичность.ВашаДатаРождения = InputBox("Введите дату рождения ") Личности.AddPerson ЭтоЛичность Loop Until MsgBox("Продолжим создавать список личностей?", vbYesNo) = vbNo Личности.PrintList End If End Sub
На этом мы и поставим точку в этой лекции.