Посетитель (шаблон проектирования)
Посетитель | |
---|---|
Visitor | |
Тип | поведенческий |
Назначение | не изменяя основного класса, добавить в него новые операции. |
Структура |
|
Применяется в случаях | когда необходимо для ряда классов сделать похожую (одну и ту же) операцию. |
Плюсы | |
Минусы |
|
Описан в Design Patterns | Да |
Посетитель (англ. visitor) — поведенческий шаблон проектирования, описывающий операцию, которая выполняется над объектами других классов. При изменении visitor нет необходимости изменять обслуживаемые классы.
Шаблон демонстрирует классический приём восстановления информации о потерянных типах, не прибегая к понижающему приведению типов.
Решаемая проблема
[править | править код]Необходимо сделать какие-то несвязные операции над рядом объектов, но нужно избежать загрязнения их кода. И нет возможности или желания запрашивать тип каждого узла и осуществлять приведение указателя к правильному типу, прежде чем выполнить нужную операцию.
Задача
[править | править код]Над каждым объектом некоторой структуры выполняется одна или более операций. Нужно определить новую операцию, не изменяя классы объектов.
Решение
[править | править код]Для независимости посетитель имеет отдельную иерархию. Структуры имеют некий интерфейс взаимодействия.
Использование
[править | править код]Если есть вероятность изменения иерархии обслуживаемого класса, либо она будет нестабильной или открытый интерфейс достаточно эффективен для доступа шаблона, то его использование будет вредоносным.
Создается базовый класс Visitor
с методами visit()
для каждого подкласса родительского Element
. Добавьте метод accept(visitor)
в иерархию Element. Для каждой операции, которая должна выполняться для объектов Element
, создайте производный от Visitor
класс. Реализации метода visit()
должны использовать открытый интерфейс класса Element
. В результате: клиенты создают объекты Visitor
и передают их каждому объекту Element
, вызывая accept()
.
Рекомендации
[править | править код]Шаблон следует использовать, если:
- имеются различные объекты разных классов с разными интерфейсами, но над ними нужно совершать операции, зависящие от конкретных классов;
- необходимо над структурой выполнить различные, усложняющие структуру операции;
- часто добавляются новые операции над структурой.
Преимущества и недостатки
[править | править код]Преимущества:
- упрощается добавление новых операций;
- объединение родственных операции в классе
Visitor
; - класс
Visitor
может запоминать в себе какое-то состояние по ходу обхода контейнера.
Недостатки:
- затруднено добавление новых классов, поскольку нужно обновлять иерархию посетителя и его сыновей.
Реализация
[править | править код]- Добавьте метод
accept(Visitor)
в иерархию «элемент». - Создайте базовый класс
Visitor
и определите методыvisit()
для каждого типа элемента. - Создайте производные классы
Visitor
для каждой операции, исполняемой над элементами. - Клиент создаёт объект
Visitor
и передаёт его в вызываемый методaccept().
#include <iostream>
#include <string>
template<typename TYPE,size_t COUNT>
inline size_t lenof(TYPE(&)[COUNT]){return COUNT;}
class Foo;
class Bar;
class Baz;
class Visitor{
public:
virtual void visit(Foo&ref)=0;
virtual void visit(Bar&ref)=0;
virtual void visit(Baz&ref)=0;
};
class Element{
public:
virtual void accept(Visitor&v)=0;
};
class Foo:public Element{
public:
void accept(Visitor&v){v.visit(*this);}
};
class Bar:public Element{
public:
void accept(Visitor&v){v.visit(*this);}
};
class Baz:public Element{
public:
void accept(Visitor&v){v.visit(*this);}
};
class GetType:public Visitor{
public:
std::string value;
public:
void visit(Foo&ref){value="Foo";}
void visit(Bar&ref){value="Bar";}
void visit(Baz&ref){value="Baz";}
};
int main()
{
Foo foo; Bar bar; Baz baz;
Element*elements[]={&foo,&bar,&baz};
for(size_t i=0;i<lenof(elements);i++)
{
GetType visitor;
elements[i]->accept(visitor);
std::cout<<visitor.value<<std::endl;
}
return 0;
}
public class Demo {
public static void main ( String [] args ) {
Point p = new Point2d( 1, 2 );
Visitor v = new Chebyshev();
p.accept( v );
System.out.println( p.getMetric() );
}
}
interface Visitor {
public void visit ( Point2d p );
public void visit ( Point3d p );
}
abstract class Point {
public abstract void accept ( Visitor v );
private double metric = -1;
public double getMetric () {
return metric;
}
public void setMetric ( double metric ) {
this.metric = metric;
}
}
class Point2d extends Point {
public Point2d ( double x, double y ) {
this.x = x;
this.y = y;
}
public void accept ( Visitor v ) {
v.visit( this );
}
private double x;
public double getX () { return x; }
private double y;
public double getY () { return y; }
}
class Point3d extends Point {
public Point3d ( double x, double y, double z ) {
this.x = x;
this.y = y;
this.z = z;
}
public void accept ( Visitor v ) {
v.visit( this );
}
private double x;
public double getX () { return x; }
private double y;
public double getY () { return y; }
private double z;
public double getZ () { return z; }
}
class Euclid implements Visitor {
public void visit ( Point2d p ) {
p.setMetric( Math.sqrt( p.getX()*p.getX() + p.getY()*p.getY() ) );
}
public void visit ( Point3d p ) {
p.setMetric( Math.sqrt( p.getX()*p.getX() + p.getY()*p.getY() + p.getZ()*p.getZ() ) );
}
}
class Chebyshev implements Visitor {
public void visit ( Point2d p ) {
double ax = Math.abs( p.getX() );
double ay = Math.abs( p.getY() );
p.setMetric( ax>ay ? ax : ay );
}
public void visit ( Point3d p ) {
double ax = Math.abs( p.getX() );
double ay = Math.abs( p.getY() );
double az = Math.abs( p.getZ() );
double max = ax>ay ? ax : ay;
if ( max<az ) max = az;
p.setMetric( max );
}
}
public static class Demo
{
private static void Main()
{
Point p = new Point2D(1, 2);
IVisitor v = new Chebyshev();
p.Accept(v);
Console.WriteLine(p.Metric);
}
}
internal interface IVisitor
{
void Visit(Point2D p);
void Visit(Point3D p);
}
internal abstract class Point
{
public double Metric { get; set; } = -1;
public abstract void Accept(IVisitor visitor);
}
internal class Point2D : Point
{
public Point2D(double x, double y)
{
X = x;
Y = y;
}
public double X { get; }
public double Y { get; }
public override void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
internal class Point3D : Point
{
public Point3D(double x, double y, double z)
{
X = x;
Y = y;
Z = z;
}
public double X { get; }
public double Y { get; }
public double Z { get; }
public override void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
internal class Euclid : IVisitor
{
public void Visit(Point2D p)
{
p.Metric = Math.Sqrt(p.X*p.X + p.Y*p.Y);
}
public void Visit(Point3D p)
{
p.Metric = Math.Sqrt(p.X*p.X + p.Y*p.Y + p.Z*p.Z);
}
}
internal class Chebyshev : IVisitor
{
public void Visit(Point2D p)
{
var ax = Math.Abs(p.X);
var ay = Math.Abs(p.Y);
p.Metric = ax > ay ? ax : ay;
}
public void Visit(Point3D p)
{
var ax = Math.Abs(p.X);
var ay = Math.Abs(p.Y);
var az = Math.Abs(p.Z);
var max = ax > ay ? ax : ay;
if (max < az) max = az;
p.Metric = max;
}
}
<?php
interface Visitor {
public function visit ( Point $point );
}
abstract class Point {
public abstract function accept ( Visitor $visitor );
private $_metric = -1;
public function getMetric () {
return $this->_metric;
}
public function setMetric ( $metric ) {
$this->_metric = $metric;
}
}
class Point2d extends Point {
public function __construct ( $x, $y ) {
$this->_x = $x;
$this->_y = $y;
}
public function accept ( Visitor $visitor ) {
$visitor->visit( $this );
}
private $_x;
public function getX () { return $this->_x; }
private $_y;
public function getY () { return $this->_y; }
}
class Point3d extends Point {
public function __construct ( $x, $y, $z ) {
$this->_x = $x;
$this->_y = $y;
$this->_z = $z;
}
public function accept ( Visitor $visitor ) {
$visitor->visit( $this );
}
private $_x;
public function getX () { return $this->_x; }
private $_y;
public function getY () { return $this->_y; }
private $_z;
public function getZ () { return $this->_z; }
}
class Euclid implements Visitor {
public function visit ( Point $p ) {
if($p instanceof Point2d)
$p->setMetric( sqrt( $p->getX()*$p->getX() + $p->getY()*$p->getY() ) );
elseif( $p instanceof Point3d)
$p->setMetric( sqrt( $p->getX()*$p->getX() + $p->getY()*$p->getY() + $p->getZ()*$p->getZ() ) );
}
}
class Chebyshev implements Visitor {
public function visit ( Point $p ) {
if($p instanceof Point2d){
$ax = abs( $p->getX() );
$ay = abs( $p->getY() );
$p->setMetric( $ax>$ay ? $ax : $ay );
}
elseif( $p instanceof Point3d){
$ax = abs( $p->getX() );
$ay = abs( $p->getY() );
$az = abs( $p->getZ() );
$max = $ax>$ay ? $ax : $ay;
if ( $max<$az ) $max = $az;
$p->setMetric( $max );
}
}
}
function start(){
$p = new Point2d( 1, 2 );
$v = new Chebyshev();
$p->accept( $v );
echo ( $p->getMetric() );
};
start();
Ссылки
[править | править код]- Robert C. Martin, Prentice Hall. The visitor family of design patterns by Robert C. Martin - a rough chapter from «The principles, patterns, and practices of agile software development» (англ.) . Архивировано 5 апреля 2012 года.
- Шаблон проектирования visitor (посетитель). Назначение, описание, особенности и реализация на С++.