![](/user_photo/2706_HbeT2.jpg)
- •А.А. Волосевич
- •3. Шаблоны и архитектура программ
- •3.1. Модульное тестирование
- •3.2. Шаблоны проектирования
- •3.3. Структурные шаблоны: Декоратор, Заместитель, мост Декоратор (Decorator)
- •Заместитель (Proxy)
- •Мост (Bridge)
- •3.4. Структурные шаблоны: компоновщик и приспособленец Компоновщик (Composite)
- •Приспособленец (Flyweight)
- •3.5. Структурные шаблоны: адаптер и фасад Адаптер (Adapter)
- •Фасад (Façade)
- •3.6. Порождающие шаблоны: прототип, фабричный метод, одиночка Прототип (Prototype)
- •Фабричный метод (Factory method)
- •Одиночка (Singleton)
- •3.7. Порождающие шаблоны: абстрактная фабрика и строитель Абстрактная фабрика (Abstract factory)
- •Строитель (Builder)
- •3.8. Шаблоны поведения: стратегия, состояние, шаблонный метод Стратегия (Strategy)
- •Состояние (State)
- •Шаблонный метод (Template method)
- •3.9. Шаблоны поведения: цепочка обязанностей и команда Цепочка обязанностей (Chain of responsibility)
- •Команда (Command)
- •3.10. Шаблоны поведения: итератор, посредник, наблюдатель Итератор (Iterator)
- •Посредник (Mediator)
- •Наблюдатель (Observer)
- •3.11. Шаблоны поведения: посетитель, интерпретатор, хранитель Посетитель (Visitor)
- •Интерпретатор (Interpreter)
- •Хранитель (Memento)
- •3.12. Некоторые неклассические шаблоны проектирования
- •Неизменный объект (Immutable object)
- •Пул объектов (Object pool)
- •Отложенная инициализация (Lazy initialization)
- •Нулевой объект (Null object)
- •3.13. Антипаттерны
- •3.14. Архитектура прогРаммного Обеспечения
- •«Клиент-сервер»
- •Архитектура, основанная на использовании компонентов
- •Многоуровневая архитектура
- •Шина сообщений
- •Выделенное представление
- •Объектно-ориентированная архитектура
- •Архитектура, ориентированная на сервисы
3.11. Шаблоны поведения: посетитель, интерпретатор, хранитель Посетитель (Visitor)
Шаблон Посетитель служит для выполнения операций над всеми объектами, объединёнными в некоторую структуру. При этом выполняемые операции не обязательно должны являться частью объектов структуры.
Очень часто в программах встречаются сложные структуры, представляющие собой дерево или граф, состоящий из разнотипных узлов. И, конечно же, при этом имеется необходимость обрабатывать такой граф или дерево. Самое очевидное решение – добавить в базовый класс узла виртуальный метод, перекрываемый в наследниках для выполнения нужного действия и осуществления дальнейшей навигации по структуре. Код, который приведён ниже, демонстрирует описанный подход.
using System;
using System.Collections.Generic;
using System.IO;
public abstract class NodeBase
{
public string Name { get; private set; }
protected NodeBase(string name)
{
Name = name;
}
public abstract void Print(TextWriter writer);
}
public class SimpleNode : NodeBase
{
public SimpleNode(string name) : base(name) { }
public override void Print(TextWriter writer)
{
writer.WriteLine(Name + " : Simple");
}
}
public class CompositeNode : NodeBase
{
public IList<SimpleNode> SimpleNodes { get; private set; }
public CompositeNode(string name, SimpleNode[] nodes) :
base(name)
{
SimpleNodes = Array.AsReadOnly(nodes);
}
public override void Print(TextWriter writer)
{
writer.WriteLine(Name + " : Composite");
foreach (var node in SimpleNodes)
{
node.Print(writer);
}
}
}
public class RootNode : NodeBase
{
public IList<SimpleNode> SimpleNodes { get; private set; }
public IList<CompositeNode> CompositeNodes { get; private set; }
public RootNode(SimpleNode[] simpleNodes,
CompositeNode[] compositeNodes) : base("Root")
{
SimpleNodes = Array.AsReadOnly(simpleNodes);
CompositeNodes = Array.AsReadOnly(compositeNodes);
}
public override void Print(TextWriter writer)
{
writer.WriteLine(Name + " : Root");
foreach (var node in SimpleNodes)
{
node.Print(writer);
}
foreach (var node in CompositeNodes)
{
node.Print(writer);
}
}
}
public class Program
{
private static NodeBase CreateTree()
{
return
new RootNode(
new[] { new SimpleNode("T11"),
new SimpleNode("T12"),
new SimpleNode("T13") },
new[]
{
new CompositeNode("T21",
new[] { new SimpleNode("T321"),
new SimpleNode("T322"), }),
new CompositeNode("T22",
new[] { new SimpleNode("T321"),
new SimpleNode("T322"), })
});
}
private static void Main()
{
var tree = CreateTree();
tree.Print(Console.Out);
}
}
Однако решение, применяемое в примере, имеет серьезный недостаток: в нем структура данных оказывается увязанной с обрабатывающими её алгоритмами. Если нам понадобится алгоритм, отличный от реализованного, то придётся добавлять еще один виртуальный метод. Еще хуже, если классы, составляющие дерево, содержатся в недоступном для модификации коде.
Основная идея шаблона Посетитель состоит в том, что каждый элемент объектной структуры содержит метод Accept(), который принимает на вход в качестве аргумента специальный объект, Посетитель, реализующий заранее известный интерфейс. Этот интерфейс содержит по одному методу Visit() для каждого типа узла. Метод Accept() в каждом узле должен вызывать методы Visit() для осуществления навигации по структуре.
public interface IVisitor
{
void VisitSimpleNode(SimpleNode node);
void VisitCompositeNode(CompositeNode node);
void VisitRootNode(RootNode node);
}
public abstract class NodeBase
{
// Опущен код, аналогичный предыдущему примеру
public abstract void Accept(IVisitor visitor);
}
public class SimpleNode : NodeBase
{
// Опущен код, аналогичный предыдущему примеру
public override void Accept(IVisitor visitor)
{
visitor.VisitSimpleNode(this);
}
}
public class CompositeNode : NodeBase
{
// Опущен код, аналогичный предыдущему примеру
public override void Accept(IVisitor visitor)
{
visitor.VisitCompositeNode(this);
}
}
public class RootNode : NodeBase
{
// Опущен код, аналогичный предыдущему примеру
public override void Accept(IVisitor visitor)
{
visitor.VisitRootNode(this);
}
}
Теперь для того, чтобы обеспечить работу алгоритма по объектной структуре, достаточно реализовать интерфейс IVisitor.
public class PrintVisitor : IVisitor
{
private readonly TextWriter writer;
private PrintVisitor(TextWriter writer)
{
this.writer = writer;
}
public void VisitSimpleNode(SimpleNode node)
{
writer.WriteLine(node.Name + " : Simple");
}
public void VisitCompositeNode(CompositeNode node)
{
writer.WriteLine(node.Name + " : Composite");
foreach (var n in node.SimpleNodes)
{
n.Accept(this);
}
}
public void VisitRootNode(RootNode node)
{
writer.WriteLine(node.Name + " : Root");
foreach (var n in node.SimpleNodes)
{
n.Accept(this);
}
foreach (var n in node.CompositeNodes)
{
n.Accept(this);
}
}
public static void Print(NodeBase tree, TextWriter writer)
{
tree.Accept(new PrintVisitor(writer));
}
}
Если алгоритм обхода структуры всегда один и тот же, код обхода (foreach в примере) можно перенести в метод Accept(). При этом в коде реализации посетителя осуществлять обход не нужно. Однако это снижает гибкость кода, так как другой способ обхода использовать уже не удастся.
В языках, поддерживающих перегрузку методов, её можно использовать для упрощения реализации метода Accept(). Для этого необходимо, чтобы все методы посетителя назывались одинаково и различались лишь типом параметра. В этом случае код метода Accept() будет одинаковым. Компилятор при компиляции сам выберет необходимый метод.
public interface IDoubleDispatchVisitor
{
void Visit(SimpleNode node);
void Visit(CompositeNode node);
void Visit(RootNode node);
}
public class SimpleNode : NodeBase
{
// Опущен код, , аналогичный предыдущему примеру
public override void Accept(IDoubleDispatchVisitor visitor)
{
visitor.Visit(this);
}
}
public class CompositeNode : NodeBase
{
// Опущен код, , аналогичный предыдущему примеру
public override void Accept(IDoubleDispatchVisitor visitor)
{
visitor.Visit(this);
}
}