Посетитель (шаблон проектирования)
Посетитель (англ. visitor) — поведенческий шаблон проектирования, описывающий операцию, которая выполняется над объектами других классов. При изменении visitor нет необходимости изменять обслуживаемые классы. Шаблон демонстрирует классический приём восстановления информации о потерянных типах, не прибегая к понижающему приведению типов при помощи двойной диспетчеризации. Решаемая проблемаНеобходимо сделать какие-то несвязные операции над рядом объектов, но нужно избежать загрязнения их кода. И нет возможности или желания запрашивать тип каждого узла и осуществлять приведение указателя к правильному типу, прежде чем выполнить нужную операцию. ЗадачаНад каждым объектом некоторой структуры выполняется одна или более операций. Нужно определить новую операцию, не изменяя классы объектов. РешениеДля независимости посетитель имеет отдельную иерархию. Структуры имеют некий интерфейс взаимодействия. ИспользованиеЕсли есть вероятность изменения иерархии обслуживаемого класса, либо она будет нестабильной или открытый интерфейс достаточно эффективен для доступа шаблона, то его использование будет вредоносным. Создается базовый класс РекомендацииШаблон следует использовать, если:
Преимущества и недостаткиПреимущества:
Недостатки:
Реализация
Пример реализации на 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 );
}
}
Пример реализации на Kotlin 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 <?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)
Литература
Ссылки
|