Посетитель (шаблон проектирования)
Посетитель | |
---|---|
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().
C++
#include <iostream>
#include <string>
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;
virtual ~Visitor() = default;
};
class Element {
public:
virtual void accept(Visitor &v) = 0;
virtual ~Element() = default;
};
class Foo : public Element {
public:
void accept(Visitor &v) override {
v.visit(*this);
}
};
class Bar : public Element {
public:
void accept(Visitor &v) override {
v.visit(*this);
}
};
class Baz : public Element {
public:
void accept(Visitor &v) override {
v.visit(*this);
}
};
class GetType : public Visitor {
public:
std::string value;
public:
void visit(Foo &ref) override {
value = "Foo";
}
void visit(Bar &ref) override {
value = "Bar";
}
void visit(Baz &ref) override {
value = "Baz";
}
};
int main() {
Foo foo;
Bar bar;
Baz baz;
Element *elements[] = {&foo, &bar, &baz};
for (auto elem : elements) {
GetType visitor;
elem->accept(visitor);
std::cout << visitor.value << std::endl;
}
return 0;
}
Java
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 );
}
}
C#
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
<?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();
Python
from abc import ABCMeta, abstractmethod
from typing import List
class Spy(metaclass=ABCMeta):
"""
Шпион - посетитель
"""
@abstractmethod
def visit_military_base(self, military_base: 'MilitaryBase') -> None:
"""
Посетить военную базу морского флота
"""
pass
@abstractmethod
def visit_headquarters(self, headquarters: 'Headquarters') -> None:
"""
Посетить центральный штаб армии
"""
pass
class MilitaryFacility(metaclass=ABCMeta):
"""
Военный объект - посещаемый объект
"""
@abstractmethod
def accept(self, spy: Spy) -> None:
"""
Принять шпиона-посетителя
"""
pass
class MilitaryBase(MilitaryFacility):
"""
Военная база подводного флота
"""
def __init__(self) -> None:
self._secret_draftings = 1
self._nuclear_submarines = 1
def __repr__(self) -> str:
return 'На военной базе находится {} атомных подводных лодок и {} секретных чертежей'.format(
self._nuclear_submarines, self._secret_draftings
)
def accept(self, spy: Spy) -> None:
spy.visit_military_base(self)
def remove_secret_draftings(self) -> None:
if self._secret_draftings:
self._secret_draftings -= 1
def remove_nuclear_submarine(self) -> None:
if self._nuclear_submarines:
self._nuclear_submarines -= 1
@property
def is_combat_ready(self) -> bool:
return self._nuclear_submarines > 0
class Headquarters(MilitaryFacility):
"""
Центральный штаб армии
"""
def __init__(self) -> None:
self._generals = 3
self._secret_documents = 2
def __repr__(self) -> str:
return 'В штабе находится {} генералов и {} секретных документов'.format(
self._generals, self._secret_documents
)
def accept(self, spy: Spy) -> None:
spy.visit_headquarters(self)
def remove_general(self) -> None:
if self._generals:
self._generals -= 1
def remove_secret_documents(self) -> None:
if self._secret_documents:
self._secret_documents -= 1
@property
def is_command_ready(self) -> bool:
return self._generals > 0
class ScoutSpy(Spy):
"""
Разведчик (конкретный шпион)
"""
def __init__(self):
self._collected_info = {}
# Здесь мы уже знаем конкретный тип объекта
def visit_military_base(self, military_base: MilitaryBase) -> None:
self._collected_info['base'] = 'Военная база:\n\t{}\n\tБоеготовность: {}'.format(
str(military_base),
'Да' if military_base.is_combat_ready else 'Нет'
)
def visit_headquarters(self, headquarters: Headquarters) -> None:
self._collected_info['headquarters'] = 'Центральный штаб:\n\t{}\n\tКомандование: {}'.format(
str(headquarters),
'Функционирует' if headquarters.is_command_ready else 'Не функционирует'
)
def report(self) -> str:
return 'Информация от разведчика:\n{}\n'.format(
'\n'.join(self._collected_info.values())
)
class JamesBond(Spy):
"""
Джеймс Бонд (другой конкретный шпион)
"""
def visit_military_base(self, military_base: MilitaryBase) -> None:
# Джеймс Бонд посещает военную базу
military_base.remove_secret_draftings() # похищает секретные чертежи
military_base.remove_nuclear_submarine() # и напоследок взрывает атомную подводную лодку
def visit_headquarters(self, headquarters: Headquarters) -> None:
# Джеймс Бонд посещает штаб
headquarters.remove_general() # ...
headquarters.remove_general() # ...
headquarters.remove_secret_documents() # ...
headquarters.remove_general() # последовтельно уничтожает всех генералов
headquarters.remove_secret_documents() # и похищает все секретные документы
if __name__ == '__main__':
base = MilitaryBase()
hq = Headquarters()
# Не важно какой именно MilitaryFacility
facilities = [base, hq] # type: List[MilitaryFacility]
scout = ScoutSpy()
print('Отправляем разведчика...\n')
for f in facilities:
f.accept(scout)
print(scout.report())
print('Отправляем Бонда на задание...\n')
spy = JamesBond()
for f in facilities:
f.accept(spy)
print('Отправляем разведчика обновить данные...\n')
for f in facilities:
f.accept(scout)
print(scout.report())
"""
OUTPUT:
Отправляем разведчика...
Информация от разведчика:
Центральный штаб:
В штабе находится 3 генералов и 2 секретных документов
Командование: Функционирует
Военная база:
На военной базе находится 1 атомных подводных лодок и 1 секретных чертежей
Боеготовность: Да
Отправляем Бонда на задание...
Отправляем разведчика обновить данные...
Информация от разведчика:
Центральный штаб:
В штабе находится 0 генералов и 0 секретных документов
Командование: Не функционирует
Военная база:
На военной базе находится 0 атомных подводных лодок и 0 секретных чертежей
Боеготовность: Нет
"""
Delphi
program Demo;
type
Point2D = class;
Point3D = class;
IVisitor = interface
procedure Visit(p: Point2D); overload;
procedure Visit(p: Point3D); overload;
end;
Point = class
private
FMetric: Double;
public
property Metric: Double read FMetric write FMetric;
procedure Accept(visitor: IVisitor); virtual; abstract;
end;
Point2D = class(Point)
private
FX: Double;
FY: Double;
public
property X: Double read FX;
property Y: Double read FY;
constructor Create(const x, y: Double);
procedure Accept(Visitor: IVisitor); override;
end;
Point3D = class(Point)
private
FX: Double;
FY: Double;
FZ: Double;
public
property X: Double read FX;
property Y: Double read FY;
property Z: Double read FZ;
constructor Create(const x, y, z: Double);
procedure Accept(Visitor: IVisitor); override;
end;
Euklid = class(TInterfacedObject, IVisitor)
public
procedure Visit(p: Point2D); overload;
procedure Visit(p: Point3D); overload;
end;
Chebyshev = class(TInterfacedObject, IVisitor)
public
procedure Visit(p: Point2D); overload;
procedure Visit(p: Point3D); overload;
end;
{ Point2D }
procedure Point2D.Accept(Visitor: IVisitor);
begin
Visitor.Visit(Self);
end;
constructor Point2D.Create(const x, y: Double);
begin
FX := x;
FY := y;
end;
{ Point3D }
procedure Point3D.Accept(Visitor: IVisitor);
begin
Visitor.Visit(Self);
end;
constructor Point3D.Create(const x, y, z: Double);
begin
FX := x;
FY := y;
FX := z;
end;
{ Euklid }
procedure Euklid.Visit(p: Point2D);
begin
p.Metric := Sqrt(Sqr(p.X) + Sqr(p.Y));
end;
procedure Euklid.Visit(p: Point3D);
begin
p.Metric := Sqrt(Sqr(p.X) + Sqr(p.Y) + Sqr(p.Z));
end;
{ Chebyshev }
procedure Chebyshev.Visit(p: Point2D);
var
ax, ay: Double;
begin
ax := Abs(p.X);
ay := Abs(p.Y);
if ax > ay then
p.Metric := ax
else
p.Metric := ay;
end;
procedure Chebyshev.Visit(p: Point3D);
var
ax, ay, az, max: Double;
begin
ax := Abs(p.X);
ay := Abs(p.Y);
az := Abs(p.Z);
if ax > ay then
max := ax
else
max := ay;
if max < az then
max := az;
p.Metric := max;
end;
var
p: Point;
v: IVisitor;
begin
p := Point2D.Create(1, 2);
v := Chebyshev.Create;
p.Accept(v);
WriteLn(p.Metric:0:2);
v := Euklid.Create;
p.Accept(v);
WriteLn(p.Metric:0:2);
p.Free;
ReadLn; // wait for press Enter
end.
Swift
protocol WarehouseItem {
var name: String { get set }
var isBroken: Bool { get set }
var price: Int { get set }
}
class WarehouseItemImpl: WarehouseItem {
var name: String = ""
var isBroken: Bool = false
var price: Int = 0
init(name: String, isBroken: Bool, price: Int) {
self.name = name
self.isBroken = isBroken
self.price = price
}
}
protocol Warehouse {
var items: [WarehouseItem] { get set}
func addItem(item: WarehouseItem)
func accept(visitor: BasicVisitor)
}
class WarehouseImpl: Warehouse {
var items: [WarehouseItem] = []
func addItem(item: WarehouseItem) {
items.append(item)
}
func accept(visitor: BasicVisitor) {
for item in items {
visitor.visit(item as AnyObject)
}
}
}
protocol BasicVisitor {
func visit(_ anObject: AnyObject)
}
class QualityCheckerVisitor: BasicVisitor {
func visit(_ anObject: AnyObject) {
if let obj = anObject as? WarehouseItem {
if obj.isBroken {
print("is Broken true")
} else {
print("is Broken false")
}
if let _ = anObject as? Warehouse {
print("Good Warehouse")
}
}
}
}
class PriceCheckerVisitor: BasicVisitor {
func visit(_ anObject: AnyObject) {
if let obj = anObject as? WarehouseItem {
print("\(obj.name) | Price: \(obj.price) rub.")
}
if let _ = anObject as? Warehouse {
print("Cost none")
}
}
}
// Use Visitor
let warehouse = WarehouseImpl()
warehouse.addItem(item: WarehouseItemImpl(name: "Item 1", isBroken: true, price: 100))
warehouse.addItem(item: WarehouseItemImpl(name: "Item 2", isBroken: false, price: 300))
warehouse.addItem(item: WarehouseItemImpl(name: "Item 3", isBroken: false, price: 500))
let price = PriceCheckerVisitor()
let qulity = QualityCheckerVisitor()
warehouse.accept(visitor: price)
warehouse.accept(visitor: qulity)
Литература
- Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес. Приемы объектно-ориентированного проектирования. Паттерны проектирования. — СПб.: Питер, 2001. — 368 с. — ISBN 5-272-00355-1.
Ссылки
- 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» (англ.) (недоступная ссылка). Дата обращения: 1 сентября 2010. Архивировано 4 апреля 2012 года.
- Шаблон проектирования visitor (посетитель) Архивная копия от 8 апреля 2012 на Wayback Machine. Назначение, описание, особенности и реализация на C++.
- Посетитель (Visitor) Архивная копия от 19 апреля 2016 на Wayback Machine. Описание, назначение, примеры реализации на java.