Посетитель (Visitor) — различия между версиями

Материал из Вики ИТ мехмата ЮФУ
Перейти к: навигация, поиск
(Описание)
(Достоинства и недостатки)
 
(не показано 15 промежуточных версий этого же участника)
Строка 1: Строка 1:
 
[[Страница_курса_Паттерны_проектирования| К основной странице курса]]
 
[[Страница_курса_Паттерны_проектирования| К основной странице курса]]
 
__NOTOC__
 
__NOTOC__
=== Другое название ===
 
 
=== Назначение ===
 
=== Назначение ===
 
Позволяет выполнить над каждым объектом некоторой структуры операцию, не загрязняя код класса этого объекта и не используя определение типа для каждого объекта.
 
Позволяет выполнить над каждым объектом некоторой структуры операцию, не загрязняя код класса этого объекта и не используя определение типа для каждого объекта.
Строка 11: Строка 10:
  
 
Таким образом, действия, производимые над объектами полиморфной структуры, являются внешними по отношению к самой этой структуре. Это позволяет, в частности, легко добавлять новые действия за счет реализации подклассов класса Visitor. Исходный код узлов полиморфной структуры остается при этом неизменным.
 
Таким образом, действия, производимые над объектами полиморфной структуры, являются внешними по отношению к самой этой структуре. Это позволяет, в частности, легко добавлять новые действия за счет реализации подклассов класса Visitor. Исходный код узлов полиморфной структуры остается при этом неизменным.
 +
 +
Паттерн Посетитель является одним из решений так называемой задачи '''двойной диспетчеризации'''. В этой задаче требуется выполнять определенное действие от двух параметров: типа объекта и типа операции. По-существу, возникает матрица действий с индексами, являющимися типами: первый индекс определяет тип объекта структуры, второй - тип действия.
 +
 +
===Использование ===
 +
Паттерн Посетитель следует использовать когда
 +
*в структуре присутствуют объекты многих классов, и необходимо выполнять над ними операции в зависимости от типа этих классов;
 +
*нежелательно засорять объекты структуры кодом операций, которые необходимо над ними выполнить
 +
*классы структуры меняются редко, а новые операции над элементами структуры добавляются часто.
  
 
=== Реализация ===
 
=== Реализация ===
Строка 17: Строка 24:
  
 
==== Участники====
 
==== Участники====
*
+
* '''Visitor''' - посетитель
 +
Объявляет метод Visit... для каждого класса в иерархии объектов с базовым классом Element. Например, для элемента ElementA объявляется метод VisitElementA с параметром типа ElementA. Для языков, в которых возможна перегрузка методов, для методов можно оставить одно имя Visit - они будут отличаться типом параметра:
 +
 
 +
<source lang="Csharp">
 +
void Visit(ElementA a);
 +
void Visit(ElementB b);
 +
</source>
 +
 
 +
* '''ConcreteVisitor''' - конкретный посетитель
 +
Реализует все операции Visit, объявленные в классе Visitor. Каждая операция реализует действие для объекта определенного типа, производного от Element.
 +
 
 +
Класс ConcreteVisitor реализует также контекст для посетителя и сохраняет в нем состояние между вызовами Visit
 +
 
 +
* '''Node''' - элемент
 +
Объявляет операцию Accept, принимающую посетителя в качестве аргумента
 +
 
 +
* '''ConcreteNode''' - конкретный элемент
 +
Реализует операцию Accept. Для древовидной структуры, помимо вызова метода Visit, может вызывать Accept своих потомков, обеспечивая тем самым обход дерева.
  
 
=== Пример ===
 
=== Пример ===
 +
Визитор CountIdVisitor считает количество идентификаторов в дереве выражения.
 +
 +
Здесь следует обратить внимание на то, что для нелистовых узлов в методе Visit требуется вызывать методы Accept своих узлов-потомков, реализуя обход всего дерева.
 +
 +
Следует обратить также внимание, что визитор при своей работе может пользоваться глобальными по отношению к методу Visit переменными - полями класса ConcreteVisitor.
 
<source lang="Csharp">
 
<source lang="Csharp">
</source>
+
using System;
 +
 
 +
abstract class Node
 +
{
 +
  public abstract void Accept(Visitor v);
 +
}
 +
 
 +
class NumNode: Node
 +
{
 +
  public NumNode(int n)
 +
  {
 +
    Number = n;
 +
  }
 +
  public int Number { get; set; } 
 +
  public override void Accept(Visitor v)
 +
  {
 +
    v.Visit(this);
 +
  }
 +
}
 +
 
 +
class IdNode: Node
 +
{
 +
  public IdNode(string id)
 +
  {
 +
    Ident = id;
 +
  }
 +
  public string Ident { get; set; } 
 +
  public override void Accept(Visitor v)
 +
  {
 +
    v.Visit(this);
 +
  }
 +
}
 +
 
 +
class BinOpNode: Node
 +
{
 +
  public BinOpNode(Node left, Node right, char op)
 +
  {
 +
    Left = left; Right = right;
 +
    Op = op;
 +
  }
 +
  public Node Left { get; set; } 
 +
  public Node Right { get; set; } 
 +
  public char Op { get; set; } 
 +
  public override void Accept(Visitor v)
 +
  {
 +
    Left.Accept(v);
 +
    Right.Accept(v);
 +
    v.Visit(this);
 +
  }
 +
}
 +
 
 +
interface Visitor
 +
{
 +
  void Visit(IdNode id);
 +
  void Visit(NumNode num);
 +
  void Visit(BinOpNode binop);
 +
}
 +
 
 +
class CountIdVisitor: Visitor
 +
{
 +
  public int Count { get; set; } 
 +
  public void Visit(IdNode id)
 +
  {
 +
    Count += 1;
 +
  }
 +
  public void Visit(NumNode num)
 +
  {
 +
  }
 +
  public void Visit(BinOpNode binop)
 +
  {
 +
  }
 +
}
 +
 
 +
class My
 +
{
 +
  static void Main()
 +
  {
 +
    var id1 = new IdNode("a1");
 +
    var id2 = new IdNode("a2");
 +
    var num = new NumNode(25);
 +
    var expr = new BinOpNode(id1,num,'+');
 +
    var expr1 = new BinOpNode(expr,id2,'*');
 +
    var v = new CountIdVisitor();
 +
    expr1.Accept(v);
 +
    Console.WriteLine(v.Count);
 +
  }
 +
}</source>
  
 
=== Достоинства и недостатки ===
 
=== Достоинства и недостатки ===
*  
+
* Упрощает добавление новых операций. Не требует менять классы узлов.
 
+
* Объединяет родственные операции в одном классе посетителя.
=== Варианты ===
+
* Недостаток: добавление новых элементов Node затруднено - в каждом посетителе необходимо добавить соответствующий метод. Поэтому при решении вопроса о том, следует ли использовать паттерн Посетитель, необходимо проанализировать, что будет добавляться чаще - алгоритмы для обработки всех элементов или новые типы элементов.
*
+
* Посетитель, в отличие от итератора, может обходить узлы, не связанные родственными соотношениями.
 +
* Посетители могут аккумулировать информацию о состоянии при посещении объектов структуры.
 +
* Недостаток: для реализации паттерна Посетитель приходится предоставлять открытые операции для доступа к внутреннему состоянию элементов, что является нарушением инкапсуляции.
 +
* Недостаток: необходимо переопределять все методы Visit, даже не задействованные в данном визиторе. Здесь можно сделать прослойку в виде абстрактного класса AbstractVisitor, предлагающего пустую реализацию всех медтодов Visit.

Текущая версия на 18:27, 31 июля 2014

К основной странице курса

Назначение

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

Описание

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

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

Таким образом, действия, производимые над объектами полиморфной структуры, являются внешними по отношению к самой этой структуре. Это позволяет, в частности, легко добавлять новые действия за счет реализации подклассов класса Visitor. Исходный код узлов полиморфной структуры остается при этом неизменным.

Паттерн Посетитель является одним из решений так называемой задачи двойной диспетчеризации. В этой задаче требуется выполнять определенное действие от двух параметров: типа объекта и типа операции. По-существу, возникает матрица действий с индексами, являющимися типами: первый индекс определяет тип объекта структуры, второй - тип действия.

Использование

Паттерн Посетитель следует использовать когда

  • в структуре присутствуют объекты многих классов, и необходимо выполнять над ними операции в зависимости от типа этих классов;
  • нежелательно засорять объекты структуры кодом операций, которые необходимо над ними выполнить
  • классы структуры меняются редко, а новые операции над элементами структуры добавляются часто.

Реализация

Диаграмма классов

VisitorCommon.png

Участники

  • Visitor - посетитель

Объявляет метод Visit... для каждого класса в иерархии объектов с базовым классом Element. Например, для элемента ElementA объявляется метод VisitElementA с параметром типа ElementA. Для языков, в которых возможна перегрузка методов, для методов можно оставить одно имя Visit - они будут отличаться типом параметра:

void Visit(ElementA a); 
void Visit(ElementB b);
  • ConcreteVisitor - конкретный посетитель

Реализует все операции Visit, объявленные в классе Visitor. Каждая операция реализует действие для объекта определенного типа, производного от Element.

Класс ConcreteVisitor реализует также контекст для посетителя и сохраняет в нем состояние между вызовами Visit

  • Node - элемент

Объявляет операцию Accept, принимающую посетителя в качестве аргумента

  • ConcreteNode - конкретный элемент

Реализует операцию Accept. Для древовидной структуры, помимо вызова метода Visit, может вызывать Accept своих потомков, обеспечивая тем самым обход дерева.

Пример

Визитор CountIdVisitor считает количество идентификаторов в дереве выражения.

Здесь следует обратить внимание на то, что для нелистовых узлов в методе Visit требуется вызывать методы Accept своих узлов-потомков, реализуя обход всего дерева.

Следует обратить также внимание, что визитор при своей работе может пользоваться глобальными по отношению к методу Visit переменными - полями класса ConcreteVisitor.

using System;

abstract class Node
{
  public abstract void Accept(Visitor v);
}

class NumNode: Node
{
  public NumNode(int n)
  { 
    Number = n; 
  }
  public int Number { get; set; }  
  public override void Accept(Visitor v)
  {
    v.Visit(this);
  }
}

class IdNode: Node
{
  public IdNode(string id)
  { 
    Ident = id; 
  }
  public string Ident { get; set; }  
  public override void Accept(Visitor v)
  {
    v.Visit(this);
  }
}

class BinOpNode: Node
{
  public BinOpNode(Node left, Node right, char op)
  { 
    Left = left; Right = right;
    Op = op;
  }
  public Node Left { get; set; }  
  public Node Right { get; set; }  
  public char Op { get; set; }  
  public override void Accept(Visitor v)
  {
    Left.Accept(v);
    Right.Accept(v);
    v.Visit(this);
  }
}

interface Visitor
{
  void Visit(IdNode id);
  void Visit(NumNode num);
  void Visit(BinOpNode binop);
}

class CountIdVisitor: Visitor
{
  public int Count { get; set; }  
  public void Visit(IdNode id)
  {
    Count += 1;
  }
  public void Visit(NumNode num)
  {
  }
  public void Visit(BinOpNode binop)
  {
  }
}

class My
{
  static void Main()
  {
    var id1 = new IdNode("a1");
    var id2 = new IdNode("a2");
    var num = new NumNode(25);
    var expr = new BinOpNode(id1,num,'+');
    var expr1 = new BinOpNode(expr,id2,'*');
    var v = new CountIdVisitor();
    expr1.Accept(v);
    Console.WriteLine(v.Count);
  }
}

Достоинства и недостатки

  • Упрощает добавление новых операций. Не требует менять классы узлов.
  • Объединяет родственные операции в одном классе посетителя.
  • Недостаток: добавление новых элементов Node затруднено - в каждом посетителе необходимо добавить соответствующий метод. Поэтому при решении вопроса о том, следует ли использовать паттерн Посетитель, необходимо проанализировать, что будет добавляться чаще - алгоритмы для обработки всех элементов или новые типы элементов.
  • Посетитель, в отличие от итератора, может обходить узлы, не связанные родственными соотношениями.
  • Посетители могут аккумулировать информацию о состоянии при посещении объектов структуры.
  • Недостаток: для реализации паттерна Посетитель приходится предоставлять открытые операции для доступа к внутреннему состоянию элементов, что является нарушением инкапсуляции.
  • Недостаток: необходимо переопределять все методы Visit, даже не задействованные в данном визиторе. Здесь можно сделать прослойку в виде абстрактного класса AbstractVisitor, предлагающего пустую реализацию всех медтодов Visit.