Посетитель (шаблон проектирования)
Посетитель | |
---|---|
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>
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;
}
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 );
}
}
package demo.visitor
import kotlin.math.abs
import kotlin.math.sqrt
fun main() {
val point2d: Point = Point2D(x = 1.0, y = 2.0)
val point3d: Point = Point3D(x = 1.0, y = 2.0, z = 3.0)
point3d.accept(Euclid)
point2d.accept(Chebyshev)
println("point2d = ${point2d.metric}")
println("point3d = ${point3d.metric}")
}
interface Visitor {
fun visit(point: Point2D)
fun visit(point: Point3D)
}
abstract class Point {
var metric: Double = -1.0
abstract fun accept(visitor: Visitor)
}
class Point2D(val x: Double, val y: Double) : Point() {
override fun accept(visitor: Visitor) {
visitor.visit(point = this)
}
}
class Point3D(val x: Double, val y: Double, val z: Double) : Point() {
override fun accept(visitor: Visitor) {
visitor.visit(point = this)
}
}
object Euclid : Visitor {
override fun visit(point: Point2D) {
point.metric = sqrt(x = point.x * point.x + point.y * point.y)
}
override fun visit(point: Point3D) {
point.metric = sqrt(x = point.x * point.x + point.y * point.y + point.z * point.z)
}
}
object Chebyshev : Visitor {
override fun visit(point: Point2D) {
val ax = abs(point.x)
val ay = abs(point.y)
point.metric = if (ax > ay) ax else ay
}
override fun visit(point: Point3D) {
val ax = abs(point.x)
val ay = abs(point.y)
val az = abs(point.z)
var max = if (ax > ay) ax else ay
if (max < az) max = az
point.metric = 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
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();
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 секретных чертежей
Боеготовность: Нет
"""
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.
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.