Share to: share facebook share twitter share wa share telegram print page

访问者模式

访问者模式用统一建模语言(UML)来表示。[1](p. 381)

访问者模式(英語:visitor pattern),是一种将算法与对象结构分离的软件设计模式[2]。在传统的单分派语言比如SmalltalkJavaC++之中,访问者模式被用来模拟双分派英语Double dispatch。在支持多分派的语言如CLOS之中,访问者模式就不再需要了。

概述

访问者模式是一个由许多对象构成的对象结构,这些对象的都拥有一个accept方法用来接受访问者对象;访问者是一个接口,它有一个visit方法,这个方法对访问到的对象结构中不同类型的元素作出相應的動作;在对象结构的访问过程中,visit方法遍历整个对象结构,对每一个元素都实施accept方法,在每一个元素的accept方法中回调访问者的visit方法,从而使访问者得以处理对象结构的每一个元素。我们可以针对对象结构设计不同的实在的访问者类来完成不同的操作。

结构

访问者模式的样例UML类图和序列图[3]

在上面的UML类图中:

  • ElementA类不直接实现新的运算,ElementA转而实现一个分派运算accept(visitor),它将请求分配(委托)给被接受的访问者对象(visitor.visitElementA(this))。Visitor1类实现这个运算(visitElementA(e:ElementA))。
  • ElementB 接着通过分派到visitor.visitElementB(this)实现accept(visitor)Visitor1类实现这个运算(visitElementB(e:ElementB))。

UML序列图展示了运行时交互:

  • Client对象游历一个对象结构中的元素(ElementA,ElementB)并调用accept(visitor)于每个元素之上。
  • 首先,Client调用accept(visitor)ElementA之上,它调用visitElementA(this)于被接受的visitor对象之上。这个元素自身(this)被传递给visitor,使得它可以访问ElementA(调用operationA())。
  • 此后,Client调用accept(visitor)ElementB之上,它调用visitElementB(this)visitor之上,它访问ElementB(调用operationB())。

例子

Java

Car Elements的访问者模式样例的UML示意图

Java的例子:

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) {
        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());
    }
}

输出:

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

Python

Python的例子:

class Element():
    def accept(self, visitor):
        key = '_'+type(self).__name__+'__visit'
        if key in visitor.__dir__():
            visitor.__getattribute__(key)(self)

class Body(Element): pass

class Engine(Element): pass

class Wheel(Element):
    def __init__(self, name):
        self.name = name

class Car(Element):
    def __init__(self):
        self.element_list = [
            Wheel("front left"), Wheel("front right"),
            Wheel("back left"), Wheel("back right"),
            Body(), Engine()]
    def accept(self, visitor):
        for element in self.element_list:
            element.accept(visitor)
        super().accept(visitor)

class Visitor():
    @classmethod
    def admit(cls, visitor_dict):
        for key, value in visitor_dict.items():
            type.__setattr__(cls, '_'+key+'__visit', value)

class PrintVisitor(Visitor): pass
def _print_body(self, body):
    print(f"Visiting body.")
def _print_car(self, car):
    print(f"Visiting car.")
def _print_wheel(self, wheel):
    print(f"Visiting {wheel.name} wheel.")
def _print_engine(self, engine):
    print(f"Visiting engine.")
print_visitor_dict = {
    'Body': _print_body, 'Car': _print_car,
    'Wheel': _print_wheel, 'Engine': _print_engine}
PrintVisitor.admit(print_visitor_dict)

class DoVisitor(Visitor): pass
def _do_body(self, body):
    print(f"Moving my body.")
def _do_car(self, car):
    print(f"Starting my car.")
def _do_wheel(self, wheel):
    print(f"Kicking my {wheel.name} wheel.")
def _do_engine(self, engine):
    print(f"Starting my engine.")
do_visitor_dict = {
    'Body': _do_body, 'Car': _do_car,
    'Wheel': _do_wheel, 'Engine': _do_engine}
DoVisitor.admit(do_visitor_dict)

这里的访问者的反射式实现方式,主要解决访问者模式中难于动态增加具体元素类的问题[4]。下面是其用例:

>>> car = Car()
>>> car.accept(PrintVisitor())
Visiting front left wheel.
Visiting front right wheel.
Visiting back left wheel.
Visiting back right wheel.
Visiting body.
Visiting engine.
Visiting car.
>>> car.accept(DoVisitor())
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.

C#

下面的C#例子,声明了一个独立的ExpressionPrintingVisitor类处理打印。如果想要介入一个新的具体访问者,则需要创建一个实现Visitor接口的新类,并且提供一个Visit方法的新实现。现存的类(LiteralAddition)将保持不变。

using System;

namespace Wikipedia;

public interface Visitor {
    void Visit(Literal literal);  
    void Visit(Addition addition);
}

public class ExpressionPrintingVisitor : Visitor {
    public void Visit(Literal literal) {
        Console.WriteLine(literal.Value);
    }

    public void Visit(Addition addition) {
        double leftValue = addition.Left.GetValue();
        double rightValue = addition.Right.GetValue();
        var sum = addition.GetValue();
        Console.WriteLine($"{leftValue} + {rightValue} = {sum}");
    }
 }

public abstract class Expression {
    public abstract void Accept(Visitor v);
   
    public abstract double GetValue();
}

public class Literal : Expression {
    public Literal(double value) {
        this.Value = value;
    }

    public double Value { get; set; }

    public override void Accept(Visitor v) {
        v.Visit(this);
    }
 
    public override double GetValue() {
        return Value;
    }
}

public class Addition : Expression {
    public Addition(Expression left, Expression right) {
        Left = left;
        Right = right;
    }

    public Expression Left { get; set; }
    public Expression Right { get; set; }

    public override void Accept(Visitor v) {
        Left.Accept(v);
        Right.Accept(v);
        v.Visit(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);
        Console.ReadKey();
    }
}

参考条目

引用

  1. ^ Reddy, Martin. API design for C++. Boston: Morgan Kaufmann. 2011. ISBN 978-0-12-385004-1. OCLC 704559821. 
  2. ^ Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John. Design Patterns: Elements of Reusable Object-Oriented Software. Addison Wesley. 1994. ISBN 0-201-63361-2. 
  3. ^ The Visitor design pattern - Structure and Collaboration. w3sDesign.com. [2017-08-12]. 
  4. ^ Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John. Design Patterns: Elements of Reusable Object-Oriented Software. Addison Wesley. 1994. ISBN 0-201-63361-2.
    Some of the benefits and liabilities of the Visitor pattern are as follows:
    ⒈ Visitor makes adding new operations easy. ……
    ⒊ Adding new ConcreteElement classes is hard. The Visitor pattern makes it hard to add new subclasses of Element. Each new ConcreteElement gives rise to a new abstract operation on Visitor and a corresponding implementation in every ConcreteVisitor class.
     
Prefix: a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9

Portal di Ensiklopedia Dunia

Kembali kehalaman sebelumnya