Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Шаблоны и архитектура программ.doc
Скачиваний:
12
Добавлен:
04.05.2019
Размер:
558.08 Кб
Скачать

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);

}

}