Besökares mönster - Visitor pattern

I objektorienterad programmering och programvaruteknik , den besökaren design mönster är ett sätt för separation av en algoritm från en objektstruktur på vilken den verkar. Ett praktiskt resultat av denna separation är möjligheten att lägga till nya operationer till befintliga objektstrukturer utan att ändra strukturerna. Det är ett sätt att följa principen öppen/stängd .

I huvudsak tillåter besökaren att lägga till nya virtuella funktioner i en grupp av klasser , utan att ändra klasserna. Istället skapas en besökarklass som implementerar alla lämpliga specialiseringar för den virtuella funktionen. Besökaren tar instansreferensen som input och implementerar målet genom dubbelsändning .

Översikt

Besökarens designmönster är ett av de tjugotre välkända GoF-designmönstren som beskriver hur man löser återkommande designproblem för att designa flexibel och återanvändbar objektorienterad programvara, det vill säga objekt som är lättare att implementera, ändra, testa och återanvända.

Vilka problem kan besöksdesignmönstret lösa?

  • Det bör vara möjligt att definiera en ny operation för (vissa) klasser av en objektstruktur utan att ändra klasserna.

När nya operationer behövs ofta och objektstrukturen består av många orelaterade klasser är det oflexibelt att lägga till nya underklasser varje gång en ny operation krävs eftersom "[..] distribution av alla dessa operationer över de olika nodklasserna leder till ett system som är svårt att förstå, underhålla och förändra. "

Vilken lösning beskriver besöksdesignmönstret?

  • Definiera ett separat (besökar) objekt som implementerar en operation som ska utföras på element i en objektstruktur.
  • Klienter passerar objektstrukturen och kallar en sändningsoperation acceptera (besökare) på ett element - som "skickar" (delegerar) begäran till "accepterat besöksobjekt". Besökarobjektet utför sedan operationen på elementet ("besöker elementet").

Detta gör det möjligt att skapa nya operationer oberoende av klasserna i en objektstruktur genom att lägga till nya besökarobjekt.

Se även UML -klassen och sekvensdiagrammet nedan.

Definition

The Gang of Four definierar besökaren som:

Representera [en] operation som ska utföras på element i en objektstruktur. Besökare låter dig definiera en ny operation utan att ändra klasserna för de element som den fungerar på.

Besökarens karaktär gör det till ett idealiskt mönster för att ansluta till offentliga API: er, vilket gör att dess klienter kan utföra operationer på en klass med en "besökande" klass utan att behöva ändra källan.

Användningsområden

Att flytta verksamhet till besökarklasser är fördelaktigt när

  • många orelaterade operationer på en objektstruktur krävs,
  • klasserna som utgör objektstrukturen är kända och förväntas inte förändras,
  • nya operationer måste läggas till ofta,
  • en algoritm involverar flera klasser av objektstrukturen, men det är önskvärt att hantera den på en enda plats,
  • en algoritm måste fungera i flera oberoende klasshierarkier.

En nackdel med detta mönster är dock att det gör tillägg till klasshierarkin svårare, eftersom nya klasser vanligtvis kräver att en ny visitmetod läggs till för varje besökare.

Använd exempel på fall

Tänk på utformningen av ett 2D datorstödd design (CAD) -system. I kärnan finns det flera typer som representerar geometriska grundläggande former som cirklar, linjer och bågar. Enheterna ordnas i lager, och högst upp i typhierarkin finns ritningen, som helt enkelt är en lista med lager, plus några tillagda egenskaper.

En grundläggande operation för denna typhierarki är att spara en ritning i systemets ursprungliga filformat. Vid första anblicken kan det verka acceptabelt att lägga till lokala sparningsmetoder för alla typer i hierarkin. Men det är också användbart att kunna spara ritningar i andra filformat. Att lägga till allt fler metoder för att spara i många olika filformat stör snart den relativt rena ursprungliga geometriska datastrukturen.

Ett naivt sätt att lösa detta skulle vara att upprätthålla separata funktioner för varje filformat. En sådan spara -funktion skulle ta en ritning som inmatning, korsa den och koda in i det specifika filformatet. Eftersom detta görs för varje tillagt olika format ackumuleras duplicering mellan funktionerna. Till exempel, att spara en cirkelform i ett rasterformat kräver mycket liknande kod oavsett vilken specifik rasterform som används och skiljer sig från andra primitiva former. Fallet för andra primitiva former som linjer och polygoner är liknande. Således blir koden en stor yttre slinga som passerar genom objekten, med ett stort beslutsträd inuti slingan som frågar efter objektets typ. Ett annat problem med detta tillvägagångssätt är att det är mycket lätt att missa en form i en eller flera sparare, eller en ny primitiv form introduceras, men sparrutinen implementeras endast för en filtyp och inte andra, vilket leder till kodförlängning och underhåll problem.

Istället kan besökarmönstret tillämpas. Den kodar en logisk operation på hela hierarkin i en klass som innehåller en metod per typ. I CAD -exemplet skulle varje sparfunktion implementeras som en separat Visitor -underklass. Detta skulle ta bort all dubblering av typkontroller och traversalsteg. Det skulle också få kompilatorn att klaga om en form utelämnas.

Ett annat motiv är att återanvända iterationskoden. Till exempel kan iterering över en katalogstruktur implementeras med ett besökarmönster. Detta skulle göra det möjligt att skapa filsökningar, filsäkerhetskopior, borttagning av kataloger etc. genom att implementera en besökare för varje funktion samtidigt som iterationskoden återanvänds.

Strukturera

UML -klass och sekvensdiagram

Image
Ett exempel på UML -klassdiagram och sekvensdiagram för besökarmönster.

I UML -klassdiagrammet ovan ElementAimplementerar klassen inte direkt en ny operation. Istället ElementAimplementerar en sändningsoperation accept(visitor) som "skickar" (delegerar) en begäran till "accepterat besökarobjekt" ( visitor.visitElementA(this)). De Visitor1klass implementerar funktionen ( visitElementA(e:ElementA)).
ElementBimplementerar sedan accept(visitor)genom att skicka till visitor.visitElementB(this). De Visitor1klass implementerar funktionen ( visitElementB(e:ElementB)).

De UML sekvensdiagram visar de växelverkan run-time: Det Clientobjektet korsar delar av ett objekt struktur ( ElementA,ElementB) och samtal accept(visitor)på varje element.
För det första Clientsamtalen accept(visitor)ElementA, som kallar visitElementA(this)på accepterade visitorföremål. Själva elementet ( this) skickas till visitorså att det kan "besöka" ElementA(ringa operationA()).
Därefter Clientringer samtalen accept(visitor)upp ElementB, som uppmanar visitElementB(this)till visitoratt "besök" ElementB(samtal operationB()).

Klassdiagram

Image
Besökare i Unified Modeling Language (UML)
Image
Besökare i LePUS3 ( legend )

Detaljer

Besökarmönstret kräver ett programmeringsspråk som stöder enkel sändning , vilket vanliga objektorienterade språk (som C ++ , Java , Smalltalk , Objective-C , Swift , JavaScript , Python och C# ) gör. Under detta villkor, överväga två objekt, var och en av någon klass typ; en kallas elementet , och den andra är besökare .

Den besökaren förklarar en visitmetod, som tar elementet som ett argument, för varje klass av element. Konkreta besökare härrör från besökarklassen och implementerar dessa visitmetoder, som var och en implementerar en del av algoritmen som fungerar på objektstrukturen. Algoritmens tillstånd upprätthålls lokalt av den konkreta besökarklassen.

Det elementet förklarar en acceptmetod för att acceptera en besökare, med besökaren som ett argument. Betongelement , härledda från elementklassen, implementerar acceptmetoden. I sin enklaste form är detta inte mer än en uppmaning till besökarens visitmetod. Sammansatta element, som upprätthåller en lista över underordnade objekt, itererar vanligtvis över dessa och kallar varje barns acceptmetod.

Den klienten skapar objektet struktur, direkt eller indirekt, och instansierar betong besökare. När en operation ska utföras som implementeras med hjälp av besökarmönstret, kallar den acceptmetoden för elementen på översta nivån.

När acceptmetoden anropas i programmet väljs dess implementering baserat på både elementets dynamiska typ och den statiska typen av besökaren. När den associerade visitmetoden anropas, väljs dess implementering baserat på både den dynamiska typen av besökaren och den statiska typen av elementet, som är känt inifrån implementeringen av acceptmetoden, vilket är samma som elementets dynamiska typ. (Som en bonus, om besökaren inte kan hantera ett argument av det givna elementets typ, kommer kompilatorn att fånga felet.)

Således visitväljs implementering av metoden baserat på både elementets dynamiska typ och besökarens dynamiska typ. Detta genomför effektivt dubbelsändning . För språk vars objektsystem stöder flera sändningar, inte bara enstaka sändningar, som Common Lisp eller C# via Dynamic Language Runtime (DLR), är implementeringen av besökarmönstret mycket förenklat (aka Dynamic Visitor) genom att tillåta användning av enkel funktionsöverbelastning till täcka alla ärenden som besöks. En dynamisk besökare, förutsatt att den endast arbetar med offentliga data, överensstämmer med principen öppen/stängd (eftersom den inte ändrar befintliga strukturer) och principen om ett enda ansvar (eftersom den implementerar besökarmönstret i en separat komponent).

På detta sätt kan en algoritm skrivas för att korsa en graf över element, och många olika typer av operationer kan utföras under den genomgången genom att ge olika typer av besökare att interagera med elementen baserat på de dynamiska typerna av både elementen och besökare.

C# exempel

Detta exempel deklarerar en separat ExpressionPrintingVisitorklass som sköter utskriften.

namespace Wikipedia
{
	public class ExpressionPrintingVisitor
	{
		public void PrintLiteral(Literal literal)
		{
			Console.WriteLine(literal.Value);
		}
		
		public void PrintAddition(Addition addition)
		{
			double leftValue = addition.Left.GetValue();
			double rightValue = addition.Right.GetValue();
			var sum = addition.GetValue();
			Console.WriteLine("{0} + {1} = {2}", leftValue, rightValue, sum);
		}
	}
	
	public abstract class Expression
	{	
		public abstract void Accept(ExpressionPrintingVisitor v);
		
		public abstract double GetValue();
	}

	public class Literal : Expression
	{
		public double Value { get; set; }

		public Literal(double value)
		{
			this.Value = value;
		}
		
		public override void Accept(ExpressionPrintingVisitor v)
		{
			v.PrintLiteral(this);
		}
		
		public override double GetValue()
		{
			return Value;
		}
	}

	public class Addition : Expression
	{
		public Expression Left { get; set; }
		public Expression Right { get; set; }

		public Addition(Expression left, Expression right)
		{
			Left = left;
			Right = right;
		}
		
		public override void Accept(ExpressionPrintingVisitor v)
		{
			Left.Accept(v);
			Right.Accept(v);
			v.PrintAddition(this);
		}
		
		public override double GetValue()
		{
			return Left.GetValue() + Right.GetValue();	
		}
	}

	public static class Program
	{
		public static void Main(string[] args)
		{
			// Emulate 1 + 2 + 3
			var e = new Addition(
				new Addition(
					new Literal(1),
					new Literal(2)
				),
				new Literal(3)
			);
			
			var printingVisitor = new ExpressionPrintingVisitor();
			e.Accept(printingVisitor);
		}
	}
}

Smalltalk -exempel

I det här fallet är det objektets ansvar att veta hur man skriver ut sig själv på en ström. Besökaren här är då objektet, inte strömmen.

"There's no syntax for creating a class. Classes are created by sending messages to other classes."
WriteStream subclass: #ExpressionPrinter
    instanceVariableNames: ''
    classVariableNames: ''
    package: 'Wikipedia'.

ExpressionPrinter>>write: anObject
    "Delegates the action to the object. The object doesn't need to be of any special
    class; it only needs to be able to understand the message #putOn:"
    anObject putOn: self.
    ^ anObject.

Object subclass: #Expression
    instanceVariableNames: ''
    classVariableNames: ''
    package: 'Wikipedia'.

Expression subclass: #Literal
    instanceVariableNames: 'value'
    classVariableNames: ''
    package: 'Wikipedia'.

Literal class>>with: aValue
    "Class method for building an instance of the Literal class"
    ^ self new
        value: aValue;
        yourself.

Literal>>value: aValue
  "Setter for value"
  value := aValue.

Literal>>putOn: aStream
    "A Literal object knows how to print itself"
    aStream nextPutAll: value asString.

Expression subclass: #Addition
    instanceVariableNames: 'left right'
    classVariableNames: ''
    package: 'Wikipedia'.

Addition class>>left: a right: b
    "Class method for building an instance of the Addition class"
    ^ self new
        left: a;
        right: b;
        yourself.

Addition>>left: anExpression
    "Setter for left"
    left := anExpression.

Addition>>right: anExpression
    "Setter for right"
    right := anExpression.

Addition>>putOn: aStream
    "An Addition object knows how to print itself"
    aStream nextPut: $(.
    left putOn: aStream.
    aStream nextPut: $+.
    right putOn: aStream.
    aStream nextPut: $).

Object subclass: #Program
    instanceVariableNames: ''
    classVariableNames: ''
    package: 'Wikipedia'.

Program>>main
    | expression stream |
    expression := Addition
                    left: (Addition
                            left: (Literal with: 1)
                            right: (Literal with: 2))
                    right: (Literal with: 3).
    stream := ExpressionPrinter on: (String new: 100).
    stream write: expression.
    Transcript show: stream contents.
    Transcript flush.

C ++ exempel

Källor

#include <iostream>
#include <vector>

class AbstractDispatcher;  // Forward declare AbstractDispatcher

class File {  // Parent class for the elements (ArchivedFile, SplitFile and
              // ExtractedFile)
 public:
  // This function accepts an object of any class derived from
  // AbstractDispatcher and must be implemented in all derived classes
  virtual void Accept(AbstractDispatcher& dispatcher) = 0;
};

// Forward declare specific elements (files) to be dispatched
class ArchivedFile;
class SplitFile;
class ExtractedFile;

class AbstractDispatcher {  // Declares the interface for the dispatcher
 public:
  // Declare overloads for each kind of a file to dispatch
  virtual void Dispatch(ArchivedFile& file) = 0;
  virtual void Dispatch(SplitFile& file) = 0;
  virtual void Dispatch(ExtractedFile& file) = 0;
};

class ArchivedFile : public File {  // Specific element class #1
 public:
  // Resolved at runtime, it calls the dispatcher's overloaded function,
  // corresponding to ArchivedFile.
  void Accept(AbstractDispatcher& dispatcher) override {
    dispatcher.Dispatch(*this);
  }
};

class SplitFile : public File {  // Specific element class #2
 public:
  // Resolved at runtime, it calls the dispatcher's overloaded function,
  // corresponding to SplitFile.
  void Accept(AbstractDispatcher& dispatcher) override {
    dispatcher.Dispatch(*this);
  }
};

class ExtractedFile : public File {  // Specific element class #3
 public:
  // Resolved at runtime, it calls the dispatcher's overloaded function,
  // corresponding to ExtractedFile.
  void Accept(AbstractDispatcher& dispatcher) override {
    dispatcher.Dispatch(*this);
  }
};

class Dispatcher : public AbstractDispatcher {  // Implements dispatching of all
                                                // kind of elements (files)
 public:
  void Dispatch(ArchivedFile&) override {
    std::cout << "dispatching ArchivedFile" << std::endl;
  }

  void Dispatch(SplitFile&) override {
    std::cout << "dispatching SplitFile" << std::endl;
  }

  void Dispatch(ExtractedFile&) override {
    std::cout << "dispatching ExtractedFile" << std::endl;
  }
};

int main() {
  ArchivedFile archived_file;
  SplitFile split_file;
  ExtractedFile extracted_file;

  std::vector<File*> files = {
      &archived_file,
      &split_file,
      &extracted_file,
  };

  Dispatcher dispatcher;
  for (File* file : files) {
    file->Accept(dispatcher);
  }
}

Produktion

dispatching ArchivedFile
dispatching SplitFile
dispatching ExtractedFile

Gå exempel

Go stöder inte överbelastning, så besöksmetoderna behöver olika namn.

Källor

package main

import "fmt"

type Visitor interface {
	visitWheel(wheel Wheel) string
	visitEngine(engine Engine) string
	visitBody(body Body) string
	visitCar(car Car) string
}

type element interface {
	Accept(visitor Visitor) string
}

type Wheel struct {
	name string
}

func (w *Wheel) Accept(visitor Visitor) string {
	return visitor.visitWheel(*w)
}

func (w *Wheel) getName() string {
	return w.name
}

type Engine struct{}

func (e *Engine) Accept(visitor Visitor) string {
	return visitor.visitEngine(*e)
}

type Body struct{}

func (b *Body) Accept(visitor Visitor) string {
	return visitor.visitBody(*b)
}

type Car struct {
	engine Engine
	body   Body
	wheels [4]Wheel
}

func (c *Car) Accept(visitor Visitor) string {
	elements := []element{
		&c.engine,
		&c.body,
		&c.wheels[0],
		&c.wheels[1],
		&c.wheels[2],
		&c.wheels[3],
	}
	res := visitor.visitCar(*c)
	for _, elem := range elements {
		res += elem.Accept(visitor)
	}
	return res
}

type PrintVisitor struct{}

func (pv *PrintVisitor) visitWheel(wheel Wheel) string {
	return fmt.Sprintln("visiting", wheel.getName(), "wheel")
}
func (pv *PrintVisitor) visitEngine(engine Engine) string {
	return fmt.Sprintln("visiting engine")
}
func (pv *PrintVisitor) visitBody(body Body) string {
	return fmt.Sprintln("visiting body")
}
func (pv *PrintVisitor) visitCar(car Car) string {
	return fmt.Sprintln("visiting car")
}

/* output:
visiting car
visiting engine
visiting body
visiting front left wheel
visiting front right wheel
visiting back left wheel
visiting back right wheel
*/
func main() {
	car := Car{
		engine: Engine{},
		body:   Body{},
		wheels: [4]Wheel{
			{"front left"},
			{"front right"},
			{"back left"},
			{"back right"},
		},
	}

	visitor := PrintVisitor{}
	res := car.Accept(&visitor)
	fmt.Println(res)
}

Produktion

visiting car
visiting engine
visiting body
visiting front left wheel
visiting front right wheel
visiting back left wheel
visiting back right wheel

Java -exempel

Följande exempel är på språket Java och visar hur innehållet i ett nodsträd (i det här fallet som beskriver bilens komponenter) kan skrivas ut. Istället för att skapa printmetoder för varje nod subklass ( Wheel, Engine, Body, och Car), en klass besökare ( CarElementPrintVisitor) utför den erforderliga utskrift verkan. Eftersom olika nodklasser kräver lite olika åtgärder för att skriva ut korrekt CarElementPrintVisitorskickas åtgärder utifrån klassen av argumentet som skickas till dess visitmetod. CarElementDoVisitor, vilket är analogt med en sparoperation för ett annat filformat, gör likadant.

Diagram

UML -diagram över exemplet med besökarmönster med bilelement

Källor

import java.util.List;

interface CarElement {
    void accept(CarElementVisitor visitor);
}

interface CarElementVisitor {
    void visit(Body body);
    void visit(Car car);
    void visit(Engine engine);
    void visit(Wheel wheel);
}

class Wheel implements CarElement {
  private final String name;

  public Wheel(final String name) {
      this.name = name;
  }

  public String getName() {
      return name;
  }

  @Override
  public void accept(CarElementVisitor visitor) {
      /*
       * accept(CarElementVisitor) in Wheel implements
       * accept(CarElementVisitor) in CarElement, so the call
       * to accept is bound at run time. This can be considered
       * the *first* dispatch. However, the decision to call
       * visit(Wheel) (as opposed to visit(Engine) etc.) can be
       * made during compile time since 'this' is known at compile
       * time to be a Wheel. Moreover, each implementation of
       * CarElementVisitor implements the visit(Wheel), which is
       * another decision that is made at run time. This can be
       * considered the *second* dispatch.
       */
      visitor.visit(this);
  }
}

class Body implements CarElement {
  @Override
  public void accept(CarElementVisitor visitor) {
      visitor.visit(this);
  }
}

class Engine implements CarElement {
  @Override
  public void accept(CarElementVisitor visitor) {
      visitor.visit(this);
  }
}

class Car implements CarElement {
    private final List<CarElement> elements;

    public Car() {
        this.elements = List.of(
            new Wheel("front left"), new Wheel("front right"),
            new Wheel("back left"), new Wheel("back right"),
            new Body(), new Engine()
        );
    }

    @Override
    public void accept(CarElementVisitor visitor) {
        for (CarElement element : elements) {
            element.accept(visitor);
        }
        visitor.visit(this);
    }
}

class CarElementDoVisitor implements CarElementVisitor {
    @Override
    public void visit(Body body) {
        System.out.println("Moving my body");
    }

    @Override
    public void visit(Car car) {
        System.out.println("Starting my car");
    }

    @Override
    public void visit(Wheel wheel) {
        System.out.println("Kicking my " + wheel.getName() + " wheel");
    }

    @Override
    public void visit(Engine engine) {
        System.out.println("Starting my engine");
    }
}

class CarElementPrintVisitor implements CarElementVisitor {
    @Override
    public void visit(Body body) {
        System.out.println("Visiting body");
    }

    @Override
    public void visit(Car car) {
        System.out.println("Visiting car");
    }

    @Override
    public void visit(Engine engine) {
        System.out.println("Visiting engine");
    }

    @Override
    public void visit(Wheel wheel) {
        System.out.println("Visiting " + wheel.getName() + " wheel");
    }
}

public class VisitorDemo {
    public static void main(final String[] args) {
        Car car = new Car();

        car.accept(new CarElementPrintVisitor());
        car.accept(new CarElementDoVisitor());
    }
}


Produktion

Visiting front left wheel
Visiting front right wheel
Visiting back left wheel
Visiting back right wheel
Visiting body
Visiting engine
Visiting car
Kicking my front left wheel
Kicking my front right wheel
Kicking my back left wheel
Kicking my back right wheel
Moving my body
Starting my engine
Starting my car

Vanligt Lisp -exempel

Källor

(defclass auto ()
  ((elements :initarg :elements)))

(defclass auto-part ()
  ((name :initarg :name :initform "<unnamed-car-part>")))

(defmethod print-object ((p auto-part) stream)
  (print-object (slot-value p 'name) stream))

(defclass wheel (auto-part) ())

(defclass body (auto-part) ())

(defclass engine (auto-part) ())

(defgeneric traverse (function object other-object))

(defmethod traverse (function (a auto) other-object)
  (with-slots (elements) a
    (dolist (e elements)
      (funcall function e other-object))))

;; do-something visitations

;; catch all
(defmethod do-something (object other-object)
  (format t "don't know how ~s and ~s should interact~%" object other-object))

;; visitation involving wheel and integer
(defmethod do-something ((object wheel) (other-object integer))
  (format t "kicking wheel ~s ~s times~%" object other-object))

;; visitation involving wheel and symbol
(defmethod do-something ((object wheel) (other-object symbol))
  (format t "kicking wheel ~s symbolically using symbol ~s~%" object other-object))

(defmethod do-something ((object engine) (other-object integer))
  (format t "starting engine ~s ~s times~%" object other-object))

(defmethod do-something ((object engine) (other-object symbol))
  (format t "starting engine ~s symbolically using symbol ~s~%" object other-object))

(let ((a (make-instance 'auto
                        :elements `(,(make-instance 'wheel :name "front-left-wheel")
                                    ,(make-instance 'wheel :name "front-right-wheel")
                                    ,(make-instance 'wheel :name "rear-left-wheel")
                                    ,(make-instance 'wheel :name "rear-right-wheel")
                                    ,(make-instance 'body :name "body")
                                    ,(make-instance 'engine :name "engine")))))
  ;; traverse to print elements
  ;; stream *standard-output* plays the role of other-object here
  (traverse #'print a *standard-output*)

  (terpri) ;; print newline

  ;; traverse with arbitrary context from other object
  (traverse #'do-something a 42)

  ;; traverse with arbitrary context from other object
  (traverse #'do-something a 'abc))

Produktion

"front-left-wheel"
"front-right-wheel"
"rear-left-wheel"
"rear-right-wheel"
"body"
"engine"
kicking wheel "front-left-wheel" 42 times
kicking wheel "front-right-wheel" 42 times
kicking wheel "rear-left-wheel" 42 times
kicking wheel "rear-right-wheel" 42 times
don't know how "body" and 42 should interact
starting engine "engine" 42 times
kicking wheel "front-left-wheel" symbolically using symbol ABC
kicking wheel "front-right-wheel" symbolically using symbol ABC
kicking wheel "rear-left-wheel" symbolically using symbol ABC
kicking wheel "rear-right-wheel" symbolically using symbol ABC
don't know how "body" and ABC should interact
starting engine "engine" symbolically using symbol ABC

Anteckningar

Den other-objectparametern är överflödig i traverse. Anledningen är att det är möjligt att använda en anonym funktion som kallar önskad målmetod med ett lexiskt fångat objekt:

(defmethod traverse (function (a auto)) ;; other-object removed
  (with-slots (elements) a
    (dolist (e elements)
      (funcall function e)))) ;; from here too

  ;; ...

  ;; alternative way to print-traverse
  (traverse (lambda (o) (print o *standard-output*)) a)

  ;; alternative way to do-something with
  ;; elements of a and integer 42
  (traverse (lambda (o) (do-something o 42)) a)

Nu sker multipelförsändelsen i samtalet från den anonyma funktionens kropp, och det traverseär bara en mappningsfunktion som distribuerar en funktionsapplikation över elementen i ett objekt. Således försvinner alla spår av besökarmönstret, förutom kartfunktionen, där det inte finns några tecken på att två objekt är inblandade. All kunskap om att det finns två objekt och en leverans på deras typer finns i lambda -funktionen.

Python -exempel

Python stöder inte metodöverbelastning i klassisk mening (polymorf beteende beroende på typ av godkända parametrar), så "besök" -metoderna för de olika modelltyperna måste ha olika namn.

Källor

"""
Visitor pattern example.
"""

from abc import ABCMeta, abstractmethod

NOT_IMPLEMENTED = "You should implement this."

class CarElement:
    __metaclass__ = ABCMeta
    @abstractmethod
    def accept(self, visitor):
        raise NotImplementedError(NOT_IMPLEMENTED)

class Body(CarElement):
    def accept(self, visitor):
        visitor.visitBody(self)

class Engine(CarElement):
    def accept(self, visitor):
        visitor.visitEngine(self)

class Wheel(CarElement):
    def __init__(self, name):
        self.name = name
    def accept(self, visitor):
        visitor.visitWheel(self)

class Car(CarElement):
    def __init__(self):
        self.elements = [
            Wheel("front left"), Wheel("front right"),
            Wheel("back left"), Wheel("back right"),
            Body(), Engine()
        ]

    def accept(self, visitor):
        for element in self.elements:
            element.accept(visitor)
        visitor.visitCar(self)

class CarElementVisitor:
    __metaclass__ = ABCMeta
    @abstractmethod
    def visitBody(self, element):
        raise NotImplementedError(NOT_IMPLEMENTED)
    @abstractmethod
    def visitEngine(self, element):
        raise NotImplementedError(NOT_IMPLEMENTED)
    @abstractmethod
    def visitWheel(self, element):
        raise NotImplementedError(NOT_IMPLEMENTED)
    @abstractmethod
    def visitCar(self, element):
        raise NotImplementedError(NOT_IMPLEMENTED)

class CarElementDoVisitor(CarElementVisitor):
    def visitBody(self, body):
        print("Moving my body.")
    def visitCar(self, car):
        print("Starting my car.")
    def visitWheel(self, wheel):
        print("Kicking my {} wheel.".format(wheel.name))
    def visitEngine(self, engine):
        print("Starting my engine.")

class CarElementPrintVisitor(CarElementVisitor):
    def visitBody(self, body):
        print("Visiting body.")
    def visitCar(self, car):
        print("Visiting car.")
    def visitWheel(self, wheel):
        print("Visiting {} wheel.".format(wheel.name))
    def visitEngine(self, engine):
        print("Visiting engine.")

car = Car()
car.accept(CarElementPrintVisitor())
car.accept(CarElementDoVisitor())

Produktion

Visiting front left wheel.
Visiting front right wheel.
Visiting back left wheel.
Visiting back right wheel.
Visiting body.
Visiting engine.
Visiting car.
Kicking my front left wheel.
Kicking my front right wheel.
Kicking my back left wheel.
Kicking my back right wheel.
Moving my body.
Starting my engine.
Starting my car.

Abstraktion

Om en använder Python 3 eller högre kan de göra en allmän implementering av acceptmetoden:

class Visitable:
    def accept(self, visitor):
        lookup = "visit_" + type(self).__qualname__.replace(".", "_")
        return getattr(visitor, lookup)(self)

Man kan utvidga detta till att iterera över klassens metodupplösningsordning om de skulle vilja falla tillbaka på redan implementerade klasser. De kan också använda subklassens krokfunktion för att definiera sökningen i förväg.

Relaterade designmönster

  • Iteratormönster - definierar en genomgångsprincip som besökarmönstret, utan att göra en typdifferentiering inom de korsade objekten
  • Kyrkokodning - ett relaterat koncept från funktionell programmering, där taggade fack-/sumtyper kan modelleras med "besökares" beteenden på sådana typer, och som gör det möjligt för besökarmönstret att efterlikna varianter och mönster .

Se även

Referenser

externa länkar