Показать меню

Посетитель (шаблон проектирования)

Посетитель (англ. 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++

    Пример реализации на 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

    Пример реализации на 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#

    Пример реализации на 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 <?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

    Пример реализации на 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'] = 'Военная база: {} Боеготовность: {}'.format( str(military_base), 'Да' if military_base.is_combat_ready else 'Нет' ) def visit_headquarters(self, headquarters: Headquarters) -> None: self._collected_info['headquarters'] = 'Центральный штаб: {} Командование: {}'.format( str(headquarters), 'Функционирует' if headquarters.is_command_ready else 'Не функционирует' ) def report(self) -> str: return 'Информация от разведчика: {} '.format( ' '.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('Отправляем разведчика... ') for f in facilities: f.accept(scout) print(scout.report()) print('Отправляем Бонда на задание... ') spy = JamesBond() for f in facilities: f.accept(spy) print('Отправляем разведчика обновить данные... ') for f in facilities: f.accept(scout) print(scout.report()) """ OUTPUT: Отправляем разведчика... Информация от разведчика: Центральный штаб: В штабе находится 3 генералов и 2 секретных документов Командование: Функционирует Военная база: На военной базе находится 1 атомных подводных лодок и 1 секретных чертежей Боеготовность: Да Отправляем Бонда на задание... Отправляем разведчика обновить данные... Информация от разведчика: Центральный штаб: В штабе находится 0 генералов и 0 секретных документов Командование: Не функционирует Военная база: На военной базе находится 0 атомных подводных лодок и 0 секретных чертежей Боеготовность: Нет """

    Delphi

    Пример реализации на 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

    Пример реализации на 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)
    Еще по этой теме:
    Сущность (информатика)
    Сущность (информатика)
    Сущность — это любой однозначно идентифицируемый конкретный или абстрактный объект, включая события и связи между объектами, информация о котором хранится и обрабатывается в базе данных (БД). В
    Математическая морфология
    Математическая морфология
    Математическая морфология (ММ) — (морфология от греч. μορφή «форма» и λογία «наука») — теория и техника анализа и обработки геометрических структур, основанная на теории множеств, топологии и
    Операция (психология)
    Операция (психология)
    Операция (англ. oрeration) - составляющая деятельности человека, соотносимая с задачей (объективно-предметными условиями достижения целей). А. Н. Леонтьев считал операцию способом осуществления
    Математическая морфология
    Математическая морфология
    Математическая морфология (ММ) — (морфология от греч. μορφή «форма» и λογία «наука») — теория и техника анализа и обработки геометрических структур, основанная на теории множеств, топологии и
    Абстрактная фабрика (шаблон проектирования)
    Абстрактная фабрика (шаблон проектирования)
    Абстрактная фабрика (англ. Abstract factory) — порождающий шаблон проектирования, предоставляет интерфейс для создания семейств взаимосвязанных или взаимозависимых объектов, не специфицируя их
    Ember.js
    Ember.js
    Ember.js — свободный каркас веб-приложений на JavaScript, реализующий шаблон MVC и нацеленный на упрощение создания масштабируемых одностраничных веб-приложений. Фактически является версией 2.0
    Комментарии:
    Добавить комментарий
    Ваше Имя:
    Ваш E-Mail: