Режим посетителя в Delphi расширяет базовый режим посетителя. Дополнительную информацию о режиме посетителя см. в [Gam+, страницы 331..344].
Представляет операцию, которая воздействует на нейтрализованные элементы структуры объекта. Он позволяет определять новые операции, которые действуют на каждый элемент без изменения его класса.
[Гам+, стр.331].
Рассмотрим инструмент объектно-ориентированного моделирования, например Rational Rose, ModelMaker, который представляет модель в виде классов и членов классов.
Инструмент моделирования предоставляет множество функций для работы с членами, таких как: составление списка всех членов класса, создание структуры кода класса, реверс-инжиниринг и т. д.
Большинство этих операций выполняют разные операции с разными элементами. Он делит члены на поля, методы,
Свойства (PRoperties). Поэтому мы должны создавать классы, которые специально обрабатывают поля, классы, которые специально обрабатывают методы, и так далее. Набор классов-членов, конечно, зависит от компилируемого языка. Но для данного языка особых изменений нет.
На рисунке показана структура некоторых классов-членов. Проблема возникает, если я разнесу все эти операции на разные классы-члены,
Это затруднит понимание, модификацию и обслуживание всей системы. Объединение генерации кода класса с проверкой членов класса создает путаницу. Некоторые классы необходимо перекомпилировать при добавлении новых операций (по крайней мере, все связанные системы также должны быть перекомпилированы). Есть способ: вы можете добавить новую операцию независимо от класса-члена в качестве операции, действующей на него.
Чтобы достичь двух вышеуказанных целей, мы можем обернуть соответствующие операции в каждом классе в независимый объект (называемый посетителем ).
И передайте этот объект текущему члену при переборе списка членов класса. Когда участник «принимает» доступ, он отправляет посетителю запрос, содержащий его собственную информацию. Член принимает себя в качестве параметра. Посетители выполняют эти действия.
Например: генератор кода, который не использует посетителей, может генерировать код с помощью абстрактного метода класса-члена: TMember.WriteInterfaceCode(Output: TStream). Каждый член вызовет WriteInterfaceCode для создания соответствующего выходного кода. Если код генерируется через посетителя, будет создан объект TinterfaceCodeVisitor и в списке участников будет вызван метод AcceptVisitor с параметром, являющимся объектом доступа. Каждый член, реализующий AcceptVisitor, будет вызывать обратно посетителя : поле вызывает метод VisitField посетителя, а метод вызывает метод VisitMethod. Таким образом, операция WriteInterfaceCode предыдущего класса Tfield теперь становится операцией VisitField TinterfaceCodeVisitor.
Чтобы посетитель мог делать больше, чем просто генерацию кода, нам нужно, чтобы у всех посетителей списка участников был абстрактный родительский класс TmemberVisitor. TmemberVisitor должен определить метод для каждого члена. Приложение, которому необходимо выводить элементы в формат HTML, определит новый подкласс TmemberVisitor, и ему больше не нужно будет добавлять код, специфичный для приложения, в класс членов. Шаблон Посетитель инкапсулирует каждую операцию в связанном Посетителе.
Используя шаблон Visitor, необходимо определить два уровня классов: один соответствует элементам, которые принимают операции (уровень Tmember), а другой — для операций над элементами (уровень TmemberVisitor). При добавлении новой операции просто добавьте новый подкласс в иерархию посетителей. Я мог бы просто определить новый подкласс TmemberVisitor, чтобы добавить новую функциональность.
Следующий код демонстрирует применение шаблона Посетитель класса Tmember, описанного выше.
тип
TMember = класс (TObject)
общественный
процедура AcceptMemberVisitor (Посетитель: TMemberVisitor виртуальный);
конец;
TField = класс (TMember)
общественный
процедура AcceptMemberVisitor (Посетитель: TMemberVisitor переопределить);
конец;
ТМетод = класс (ТМембер)
общественный
процедура AcceptMemberVisitor (Посетитель: TMemberVisitor переопределить);
конец;
TProperty = класс (TMember)
общественный
процедура AcceptMemberVisitor (Посетитель: TMemberVisitor переопределить);
конец;
TMemberVisitor = класс (TObject)
общественный
процедура VisitField (экземпляр: TField virtual);
процедура VisitMember (экземпляр: TMember virtual);
процедура VisitMethod (экземпляр: TMethod virtual);
процедура VisitProperty (экземпляр: TProperty virtual);
конец;
выполнение
{TMember}
начинать
Посетитель.VisitMember(Self);
конец;
{TField}
процедура TField.AcceptMemberVisitor(Посетитель: TMemberVisitor);
начинать
конец;
{ ТМетод }
процедура TMethod.AcceptMemberVisitor(Посетитель: TMemberVisitor);
начинать
Посетитель.VisitMethod(Self);
конец;
{TPProperty}
процедура TProperty.AcceptMemberVisitor(Посетитель: TMemberVisitor);
начинать
Посетитель.VisitProperty(Self);
конец;
{ TMemberVisitor }
процедура TMemberVisitor.VisitField(Экземпляр: TField);
начинать
конец;
процедура TMemberVisitor.VisitMember(Экземпляр: TMember);
начинать
конец;
процедура TMemberVisitor.VisitMethod(Экземпляр: TMethod);
начинать
конец;
процедура TMemberVisitor.VisitProperty(Экземпляр: TProperty);
начинать
конец;
проиллюстрировать:
? TMember, TField, TMethod и Tproperty реализуют метод AcceptMemberVisitor. Эти методы встроены в шаблон.
? Класс TMemberVisitor реализует VisitMember, VisitField и другие методы. TmemberVisitor — абстрактный класс, и все его методы реализованы в конкретных подклассах.
Ниже приведена реализация простого генератора кода.
Введение кода:
• TCodeGenerationVisitor — посетитель генератора кода, реализующего элемент.
? Посетитель определяет контекстно-зависимое свойство: Выход: TTextStream,
? Он должен быть установлен до вызова VisitXXX. Например: DrawingVisitor обычно требует контекста, включающего холст, для поддержки операций рисования. Контекст назначается генератору кода перед перебором всей пары элементов.
? Генератор кода объединит весь код сгенерированного класса.
Чтобы по-настоящему понять режим посетителя, вы можете выполнить этот пример и дополнительно изучить механизм двойной отправки: Accept/visit.
модуль CodeGenerators;
интерфейс
использует классы, TextStreams;
тип
TCodeGenerator = класс (TObject)
общественный
процедура Generate (члены: TList; вывод: TTextStream);
конец;
выполнение
использует Членов;
тип
TCodeGenerationVisitor = класс (TMemberVisitor)
частный
FВывод: TTextStream;
общественный
процедура VisitField (экземпляр: TField переопределить);
процедура VisitMethod (экземпляр: TMethod override);
процедура VisitProperty (экземпляр: TProperty переопределить);
Вывод свойства: TTextStream чтение FOutput запись FOutput;
конец;
{TCodeGenerationVisitor}
процедура TCodeGenerationVisitor.VisitField(Экземпляр: TField);
начинать
Output.WriteLnFmt(' %s: %s;', [Instance.Name, Instance.DataName]);
конец;
процедура TCodeGenerationVisitor.VisitMethod(Экземпляр: TMethod);
вар
MKStr, DTStr: строка;
начинать
случай Экземпляр.МетодВид
mkConstructor: MKStr := 'конструктор';
mkDestructor: MKStr := 'деструктор';
mkProcedure: MKStr := 'процедура';
mkFuntion: MKStr := 'функция';
конец;
если Instance.MethodKind = mkFunction, то
DTStr := ': ' + Экземпляр.ИмяДанных
еще
ДТСтр := ';
{Код неполный, его достаточно, чтобы продемонстрировать генерацию кода Tметода}
Output.WriteLnFmt(' %s %s%s%s;'
[MKStr, Экземпляр.Имя, Экземпляр.Параметры, DTStr]);
конец;
процедура TCodeGenerationVisitor.VisitProperty(Экземпляр: TProperty);
начинать
Output.WriteLnFmt(' свойство %s: %s читает %s записывает %s;',
[Экземпляр.Имя, Экземпляр.ИмяДанных,
Экземпляр.ReadSpecifier, Экземпляр.WriteSpecifier]);
конец;
{TCodeGenerator}
процедура TCodeGenerator.Generate(Члены: TList; Выход: TTextStream);
вар
Я: целое число;
начинать
{Написать определение класса}
Output.WriteLine('TSample = класс (TObject)');
{хороший! Посетители, присоединившиеся к генератору кода}
Посетитель:= TCodeGenerationVisitor.Create;
Пытаться
{Не забудьте предоставить контекст для всех посещений, чтобы облегчить доступ к методам VisitXXX. }
для I := 0 до Members.Count - 1 сделать
{Конкретный раздел кода, где произошло что-то хорошее}
TMember(Члены[I]).AcceptMemberVisitor(Посетитель);
окончательно
Посетитель.Бесплатно;
конец;
{Генерация кода для участников класса завершена}
Output.WriteLine('конец;');
конец;
Организация
//Многие выдержки из «Шаблонов проектирования»,