Посетитель (шаблон проектирования)
Посетитель | |
---|---|
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.