· Настраиваемых элементов интерфейса;
· Макрокоманд;
· Ведения и восстановления записей;
· Функций «отмена» и «повтор».
Чтобы создать настраиваемый интерфейс, форма может содержать управляющий массив кнопок. Во время выполнения программы форма может загрузить надписи на кнопках и создать соответствующий набор управляющих объектов. Когда пользователь нажимает на кнопку, обработчику событий кнопки нужно всего лишь вызвать метод Execute соответствующего управляющего объекта. Детали происходящего находятся внутри класса управляющего объекта, а не в обработчике событий.
Программа Command1 использует управляющие объекты для создания настраиваемого интерфейса для нескольких не связанных между собой функций. При нажатии на кнопку программа вызывает метод Execute соответствующего управляющего объекта.
Программа может использовать управляющие объекты для создания определенных пользователем макрокоманд. Пользователь задает последовательность действий, которые программа запоминает в коллекции в виде управляющих объектов. Когда затем пользователь вызывает макрокоманду, программа вызывает методы Execute объектов, которые находятся в коллекции.
Управляющие объекты могут обеспечивать ведение и восстановление записей. Управляющий объект может при каждом своем вызове записывать информацию о себе в лог‑файл. Если программа аварийно завершит работы, она может затем использовать записанную информацию для восстановления управляющих объектов и выполнения их для повторения последовательности команд, которая выполнялась до сбоя программы.
И, наконец, программа может использовать набор управляющих объектов для реализации функций отмены (undo) и повтора (redo).
=========362
Программа использует переменную LastCmd для отслеживания последнего управляющего объекта в коллекции. Если вы выбираете команду Undo (Отменить) в меню Draw (Рисовать), то программа уменьшает значение переменной LastCmd на единицу. Когда программа потом выводит рисунок, она вызывает только объекты, стоящие до объекта с номером LastCmd.
Если вы выбираете команду Redo (Повторить) в меню Draw, то программа увеличивает значение переменной LastCmd на единицу. Когда программа выводит рисунок, она выводит на один объект больше, чем раньше, поэтому отображается восстановленный рисунок.
При добавлении новой фигуры программа удаляет любые команды из коллекции, которые лежат после позиции LastCmd,. затем добавляет новую команду рисования в конце и запрещает команду Redo, так как нет команд, которые можно было бы отменить. На рис. 13.1 показано окно программы Command2 после добавления новой фигуры.
Контролирующий объект (visitor object) проверяет все элементы в составном объекте (aggregate object). Процедура, реализованная в составном классе, обходит все объекты, передавая каждый из них контролирующему объекту в качестве параметра.
Например, предположим, что составной объект хранит элементы в связном списке. Следующий код показывает, как его метод Visit обходит список, передавая каждый объект в качестве параметра методу Visit контролирующего объекта ListVisitor:
Public Sub Visit(obj As ListVisitor)
Dim cell As ListCell
Set cell = TopCell
Do While Not (cell Is Nothing)
obj.Visit cell
Set cell = cell.NextCell
Loop
End Sub
@Рис. 13.1. Программа Command2
=========363
Следующий код демонстрирует, как класс ListVisitor может выводить на экран значения элементов в окне Immediate (Срочно).
Public Sub Visit(cell As ListCell)
Debug.Print cell.Value
End Sub
Используя парадигму контролирующего объекта, составной класс определяет порядок, в котором обходятся элементы. Составной класс может определять несколько методов для обхода содержащих его элементов. Например, класс дерева может обеспечивать методы VisitPreorder (Прямой обход), VisitPostorder (Обратный обход), VisitInorder (Симметричный обход) и VisitBreadthFirst (Обход в глубину) для обхода элементов в различном порядке.
Итератор обеспечивает другой метод обхода элементов в составном объекте. Объект‑итератор обращается к составному объекту для обхода его элементов, и в этом случае итератор определяет порядок, в котором проверяются элементы. С составным классом могут быть сопоставлены несколько классов итераторов для того, чтобы выполнять различные обходы элементов составного класса.
Чтобы выполнить обход элементов, итератор должен представлять порядок, в котором элементы записаны, чтобы определить порядок их обхода. Если составной класс представляет собой связный список, то объект‑итератор должен знать, что элементы находятся в связном списке, и должен уметь перемещаться по списку. Так как итератору известны детали внутреннего устройства списка, это нарушает скрытие данных составного объекта.
Вместо того чтобы каждый класс, которому нужно проверять элементы составного класса, реализовал обход самостоятельно, можно сопоставить составному классу класс итератора. Класс итератора должен содержать простые процедуры MoveFirst (Переместиться в начало), MoveNext (Переместиться на следующий элемент), EndOfList (Переместиться в конец списка) и CurrentItem (Текущий элемент) для обеспечения косвенного доступа к списку. Новые классы могут включать в себя экземпляр класса итератора и использовать его методы для обхода элементов составного класса. На рис. 13.2 схематически показано, как новый объект использует объект‑итератор для связи со списком.
Программа IterTree, описанная ниже, использует итераторы для обхода полного двоичного дерева. Класс Traverser (Обходчик) содержит ссылку на объект‑итератор. Они использует обеспечиваемые итератором процедуры MoveFirst, MoveNext, CurrentCaption и EndOfTree для получения списка узлов в дереве.
@Рис. 13.2. Использование итератора для косвенной связи со списком
=========364
Итераторы нарушают скрытие соответствующих им составных объектов, в отличие от новых классов, которые содержат итераторы. Для того, чтобы избавиться от потенциальной путаницы, можно рассматривать итератор как надстройку над составным объектом.
Контролирующие объекты и итераторы обеспечивают выполнение похожих функций, используя различные подходы. Так как парадигма контролирующего объекта оставляет детали составного объекта скрытыми внутри него, она обеспечивает лучшую инкапсуляцию. Итераторы могут быть полезны, если порядок обхода может часто изменяться или он должен переопределяться во время выполнения программы. Например, составной объект может использовать методы порождающего класса (который описан позднее) для создания объекта‑итератора в процессе выполнения программы. Содержащий итератор класс не должен знать, как создается итератор, он всего лишь использует методы итератора для доступа к элементам составного объекта.
Многие классы тесно работают с другими. Например, класс итератора тесно взаимодействует с составным классом. Для выполнения работы, итератор должен нарушать скрытие составного класса. При этом, хотя эти связанные классы иногда должны нарушать скрытие данных друг друга, другие классы должны не иметь такой возможности.
Дружественный класс (friend class) — это класс, имеющий специальное разрешение нарушать скрытие данных для другого класса. Например, класс итератора является дружественным классом для соответствующего составного класса. Ему, в отличие от других классов, разрешено нарушать скрытие данных для составного класса.
В 5‑й версии Visual Basic появилось зарезервированное слово Friend для разрешения ограниченного доступа к переменным и процедурам, определенным внутри модуля. Элементы, определенные при помощи зарезервированного слова Friend, доступны внутри проекта, но не в других проектах. Например, предположим, что вы создали классы LinkedList (Связный список) и ListIterator (Итератор списка) в проекте ActiveX сервера. Программа может создать сервер связного списка для управления связными списками. Порождающий метод класса LinkedList может создавать объекты типа ListIterator для использования в программе.
Класс LinkedList может обеспечивать в программе средства для работы со связными списками. Этот класс объявляет свои свойства и методы открытыми, чтобы их можно было использовать в основной программе. Класс ListIterator позволяет программе выполнять итерации над объектами, которыми управляет класс LinkeList. Процедуры, используемые классом ListIterator для оперирования объектами LinkedList, объявляются как дружественные в модуле LinkedList. Если классы LinkedList и ListIterator создаются в одном и том же проекте, то класс ListIterator может использовать эти дружественные процедуры. Поскольку основная программа находится в другом проекте, она этого сделать не может.
Этот очень эффективный, но довольно громоздкий метод. Она требует создания двух проектов, и установки одного сервера ActiveX. Он также не работает в более ранних версиях Visual Basic.
Наиболее простой альтернативой было бы соглашение о том, что только дружественные классы могут нарушать скрытие данных друг друга. Если все разработчики будут придерживаться этого правила, то проектом все еще можно будет управлять. Тем не менее, искушение обратиться напрямую к данным класса LinkedList может быть сильным, и всегда существует вероятность, что кто‑нибудь нарушит скрытие данных из‑за лени или по неосторожности.
Другая возможность заключается в том, чтобы дружественный объект передавал себя другому классу в качестве параметра. Передавая себя в качестве параметра, дружественный класс тем самым показывает, что он является таковым. Программа Fstacks использует этот метод для реализации стеков.
=======365
При использовании этого метода все еще можно нарушить скрытие данных объекта. Программа может создать объект дружественного класса и использовать его в качестве параметра, чтобы обмануть процедуры другого объекта. Тем не менее, это достаточно громоздкий процесс, и маловероятно, что разработчик сделает так случайно.
В этой парадигме один из объектов выступает в качестве интерфейса (interface) между двумя другими. Один объект может использовать свойства и методы первого объекта для взаимодействия со вторым. Интерфейс иногда также называется адаптером (adapter), упаковщиком (wrapper), или мостом (bridge). На рис. 13.3 схематически изображена работа интерфейса.