Посетитель (Visitor)

Шаблон Посетитель служит для выполнения операций над всеми объектами, объединёнными в некоторую структуру. При этом выполняемые операции не являться частью объектов структуры.

Часто в программах встречаются сложные структуры, представляющие собой дерево или граф и состоящие из разнотипных узлов. При этом имеется необходимость обрабатывать все узлы графа или дерева. Очевидное решение – добавить в базовый класс узла виртуальный метод, перекрываемый в наследниках для выполнения нужного действия. Код, который приведён ниже, демонстрирует описанный подход.

// базовый класс для узла

public abstract class BaseNode

{

public string Name { get; private set; }

protected BaseNode(string name)

{

Name = name;

}

public abstract void Print(TextWriter writer);

}

// простой узел

public class SimpleNode: BaseNode

{

public SimpleNode(string name): base(name)

{

}

public override void Print(TextWriter writer)

{

writer.WriteLine(Name + ": Simple");

}

}

// узел с дочерними узлами

public class CompositeNode: BaseNode

{

public IList<BaseNode> Nodes { get; private set; }

public CompositeNode(string name, params BaseNode[] nodes):

base(name)

{

Nodes = Array.AsReadOnly(nodes);

}

public override void Print(TextWriter writer)

{

writer.WriteLine(Name + ": Composite");

foreach (var node in Nodes)

{

node.Print(writer);

}

}

}

// использование классов - построение и печать иерархии

var tree = new CompositeNode("Root",

new CompositeNode("Level_1",

new SimpleNode("Leaf_1_1"),

new SimpleNode("Leaf_1_2")),

new SimpleNode("Leaf_2"));

tree.Print(Console.Out);

Решение, применяемое в примере, имеет серьёзный недостаток: в нем структура данных оказывается увязанной с обрабатывающими её алгоритмами. Если нам понадобится алгоритм, отличный от реализованного, то придётся добавлять ещё один виртуальный метод. Ещё хуже, если классы, описывающие дерево, содержатся в недоступном для модификации коде.

Основная идея шаблона Посетитель состоит в том, что каждый элемент объектной структуры содержит виртуальный метод Accept(), который принимает на вход в качестве аргумента специальный объект – посетитель, реализующий заранее известный интерфейс. Этот интерфейс содержит по одному методу Visit() для каждого типа узла. Метод Accept() в каждом узле должен вызывать методы Visit() для осуществления навигации по структуре.

// интерфейс посетителя

public interface IVisitor

{

void Visit(SimpleNode node);

void Visit(CompositeNode node);

}

// конкретный посетитель

public class PrintVisitor: IVisitor

{

private readonly TextWriter _writer;

public PrintVisitor(TextWriter writer)

{

_writer = writer;

}

public void Visit(SimpleNode node)

{

_writer.WriteLine(node.Name + ": Simple");

}

public void Visit(CompositeNode node)

{

_writer.WriteLine(node.Name + ": Composite");

foreach (var node in node.Nodes)

node.Accept(this);

}

}

public abstract class BaseNode

{

public string Name { get; private set; }

protected BaseNode(string name)

{

Name = name;

}

public abstract void Accept(IVisitor visitor);

}

public class SimpleNode: BaseNode

{

public SimpleNode(string name): base(name)

{

}

public override void Accept(IVisitor visitor)

{

visitor.Visit(this);

}

}

public class CompositeNode: BaseNode

{

public IList<BaseNode> Nodes { get; private set; }

public CompositeNode(string name, params BaseNode[] nodes):

base(name)

{

Nodes = Array.AsReadOnly(nodes);

}

public override void Accept(IVisitor visitor)

{

visitor.Visit(this);

}

}

Приём, использованный в примере, носит название двойная диспетчеризация. Обычные виртуальные методы используют одинарную диспетчеризацию – то, какая операция будет выполнена, зависит от имени метода и типа объекта-получателя. В двойной диспетчеризации шаблона Посетитель вызываемая операция зависит от имени метода, типа посещаемого элемента и типа конкретного посетителя[5].


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: