Skip to content

观察者设计模式

约 587 个字 102 行代码 预计阅读时间 4 分钟

这种设计模式允许你在不修改现有对象结构(类)的前提下,向这些类添加新的操作。它通过定义一个新的“访问者”类来封装这些新操作,然后让对象结构中的每个元素“接受”(accept)这个访问者,从而执行相应的操作。

主要组成部分

  • 访问者 (Visitor) 接口/抽象类:定义了一系列 visit 方法,每个方法对应一个具体元素(ConcreteElement)类型。

    例如:visitConcreteElementA(elementA: ConcreteElementA),visitConcreteElementB(elementB: ConcreteElementB)。这个接口的实现者(具体访问者)将包含对不同类型元素的具体操作逻辑。

  • 具体访问者 (ConcreteVisitor):实现 Visitor 接口。每一个 visit 方法都实现了对相应具体元素类型的特定操作。一个具体访问者通常代表一个特定的算法或一组相关的操作。
  • 元素 (Element) 接口/抽象类:定义了一个 accept(visitor: Visitor) 方法,该方法以一个访问者对象作为参数。这个方法是连接元素和访问者的桥梁。
  • 具体元素 (ConcreteElement):实现 Element 接口。在其 accept 方法中,通常会调用访问者(visitor)的相应 visit 方法,并将自身(this)作为参数传递过去。例如:visitor.visitConcreteElementA(this)。它们通常包含一些数据,访问者会使用这些数据进行操作。

工作流程 (Double Dispatch)

  1. 客户端创建一个具体访问者对象,并将其传递给对象结构中的某个元素(或整个对象结构)。
  2. 元素调用其 accept(visitor) 方法,将自身传递给访问者。
  3. 在 accept 方法内部,元素会回调访问者的 visitConcreteElementX(this) 方法。
  4. 第一次分派:调用 element.accept(visitor)。这个调用会根据 element 的实际类型(ConcreteElementA, ConcreteElementB 等)来确定执行哪个 accept 方法。
  5. 第二次分派:在具体元素的 accept 方法内部,调用 visitor.visitConcreteElementX(this)。这个调用会根据 visitor 的实际类型(ConcreteVisitor1, ConcreteVisitor2 等)以及 this (即具体元素)的类型来确定执行哪个 visit 方法。

示例

C++
// 元素接口
interface Shape {
    accept(visitor: visitor);
}

// 具体元素
class Circle implements Shape {
    radius: number;
    constructor(radius: number) { this.radius = radius; }
    accept(visitor: visitor) {
        visitor.visitCircle(this);
    }
}

class Square implements Shape {
    side: number;
    constructor(side: number) { this.side = side; }
    accept(visitor: visitor) {
        visitor.visitSquare(this);
    }
}

// 访问者接口
interface visitor {
    visitCircle(circle: Circle);
    visitSquare(square: Square);
}

// 具体访问者:计算面积
class AreaCalculatorVisitor implements visitor {
    visitCircle(circle: Circle) {
        const area = Math.PI * circle.radius * circle.radius;
        console.log(`Area of Circle: ${area}`);
    }
    visitSquare(square: Square) {
        const area = square.side * square.side;
        console.log(`Area of Square: ${area}`);
    }
}

// 具体访问者:导出为XML
class XMLExportVisitor implements visitor {
    visitCircle(circle: Circle) {
        console.log(`<circle radius="${circle.radius}"/>`);
    }
    visitSquare(square: Square) {
        console.log(`<square side="${square.side}"/>`);
    }
}

// 对象结构 (简单列表)
const shapes: Shape[] = [
    new Circle(5),
    new Square(4)
];

// 使用
const areaCalculator = new AreaCalculatorVisitor();
const xmlExporter = new XMLExportVisitor();

console.log("Calculating areas:");
for (const shape of shapes) {
    shape.accept(areaCalculator);
}

console.log("\nExporting to XML:");
for (const shape of shapes) {
    shape.accept(xmlExporter);
}

// 输出:
// Calculating areas:
// Area of Circle: 78.53981633974483
// Area of Square: 16
//
// Exporting to XML:
// <circle radius="5"/>
// <square side="4"/>

nebula 中的 visitor pattern

  • nebula 将 Expression 类作为 element,将 find,evaluate 等功能变成 visitor 接口的实现类,实现了 visitor pattern。
C++
// interface
class ExprVisitorImpl : public ExprVisitor {
 public:
  // leaf expression nodes
  void visit(ConstantExpression *) override {}
};

// implement interface
class EvaluableExprVisitor : public ExprVisitorImpl {
  using ExprVisitorImpl::visit;

  void visit(ConstantExpression *) override;

  bool isEvaluable_{true};
  const QueryContext *qctx_{nullptr};
};

// element
class ConstantExpression : public Expression {
  friend class Expression;
  void accept(ExprVisitor* visitor) override;
 private:
  Value val_;
};