cover

Visitor 模式:把操作抽出來

Visitor 模式把對物件的操作集中到訪問者,讓你在不改動資料結構的情況下新增行為。

classDiagram
    class Element {
        <<abstract>>
        +accept(visitor)* void
    }
    class EmployeeData {
        -name : String
        -salary : Number
        -department : String
        +accept(visitor) void
    }
    class SalesData {
        -product : String
        -amount : Number
        -region : String
        +accept(visitor) void
    }
    class Visitor {
        <<interface>>
        +visitEmployee(employee)* void
        +visitSales(sales)* void
    }
    class ReportVisitor {
        -report : String[]
        +visitEmployee(employee) void
        +visitSales(sales) void
        +getReport() String
    }
    Element <|-- EmployeeData
    Element <|-- SalesData
    Visitor <|.. ReportVisitor
    Element ..> Visitor : accept(visitor)

使用情境

  1. 報告生成:訪問不同資料結構輸出不同報表。
  2. 資料分析:對各種資料節點進行統計。
  3. 檔案處理:依檔案類型套用不同操作。

實作範例

Visitor 模式:訪問者巡迴操作不同展品

class Element {
  accept(visitor) {
    throw new Error('accept must be implemented');
  }
}
 
class EmployeeData extends Element {
  constructor(name, salary, department) {
    super();
    this.name = name;
    this.salary = salary;
    this.department = department;
  }
 
  accept(visitor) {
    visitor.visitEmployee(this);
  }
}
 
class SalesData extends Element {
  constructor(product, amount, region) {
    super();
    this.product = product;
    this.amount = amount;
    this.region = region;
  }
 
  accept(visitor) {
    visitor.visitSales(this);
  }
}
 
class ReportVisitor {
  constructor() {
    this.report = [];
  }
 
  visitEmployee(employee) {
    this.report.push(
      `Employee: ${employee.name}, Department: ${employee.department}`
    );
  }
 
  visitSales(sales) {
    this.report.push(
      `Sales: ${sales.product}, Amount: $${sales.amount}`
    );
  }
 
  getReport() {
    return this.report.join('\n');
  }
}
 
const elements = [
  new EmployeeData('John', 50000, 'Engineering'),
  new EmployeeData('Jane', 60000, 'Marketing'),
  new SalesData('Product A', 10000, 'Asia'),
];
 
const visitor = new ReportVisitor();
elements.forEach(element => element.accept(visitor));
console.log(visitor.getReport());

優點

  • 新增操作不需改動資料結構
  • 操作集中管理

缺點

  • 新增元素時要改訪問者
  • 對私有成員存取受限

延伸閱讀