首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

深度解析 Java 設計模式:訪問者模式原理與實戰應用

     編輯:Java 相關知識

訪問者模式核心定義

訪問者模式將作用於某資料結構中各元素的操作封裝起來,在不改變資料結構的前提下定義作用於這些元素的新操作。屬於行為型模式,核心思想是分離資料結構與操作邏輯。

模式結構角色

  • Visitor (訪問者介面):聲明針對每種 ConcreteElement 的 visit 方法
  • ConcreteVisitor (具體訪問者):實現各元素的具體操作邏輯
  • Element (元素介面):聲明 accept(Visitor) 方法
  • ConcreteElement (具體元素):實現 accept,調用 visitor.visit(this)
  • ObjectStructure (物件結構):元素集合容器,提供遍歷介面

經典案例:購物車結算系統

元素介面與實現

public interface ItemElement {
    int accept(ShoppingCartVisitor visitor);
}

public class Book implements ItemElement {
    private int price;
    private String isbn;
    
    public Book(int price, String isbn) { this.price = price; this.isbn = isbn; }
    public int getPrice() { return price; }
    public String getIsbn() { return isbn; }
    
    @Override
    public int accept(ShoppingCartVisitor visitor) {
        return visitor.visit(this); // 雙分派關鍵點
    }
}

public class Fruit implements ItemElement {
    private int pricePerKg, weight;
    private String name;
    
    public Fruit(int pricePerKg, int weight, String name) { ... }
    public int getPricePerKg() { return pricePerKg; }
    public int getWeight() { return weight; }
    public String getName() { return name; }
    
    @Override
    public int accept(ShoppingCartVisitor visitor) {
        return visitor.visit(this);
    }
}

訪問者介面與實現

public interface ShoppingCartVisitor {
    int visit(Book book);
    int visit(Fruit fruit);
}

public class ShoppingCartVisitorImpl implements ShoppingCartVisitor {
    @Override
    public int visit(Book book) {
        int cost = book.getPrice();
        if (cost > 50) cost -= 5; // 滿 50 減 5
        System.out.println("Book ISBN:" + book.getIsbn() + " cost=" + cost);
        return cost;
    }
    
    @Override
    public int visit(Fruit fruit) {
        int cost = fruit.getPricePerKg() * fruit.getWeight();
        System.out.println(fruit.getName() + " cost=" + cost);
        return cost;
    }
}

客戶端使用

public class ShoppingCartClient {
    public static void main(String[] args) {
        ItemElement[] items = {
            new Book(20, "1234"), new Book(100, "5678"),
            new Fruit(10, 2, "Banana"), new Fruit(5, 5, "Apple")
        };
        System.out.println("總價: " + calculatePrice(items));
    }
    
    private static int calculatePrice(ItemElement[] items) {
        ShoppingCartVisitor visitor = new ShoppingCartVisitorImpl();
        int sum = 0;
        for (ItemElement item : items) {
            sum += item.accept(visitor); // 雙分派
        }
        return sum;
    }
}
// 輸出:
// Book ISBN:1234 cost=20
// Book ISBN:5678 cost=95
// Banana cost=20
// Apple cost=25
// 總價: 160

核心機制:雙分派

訪問者模式本質是模擬雙分派:

  1. 客戶端調用 element.accept(visitor) —— 第一次分派(根據元素類型)
  2. 元素調用 visitor.visit(this) —— 第二次分派(根據訪問者類型)

Java 單分派語言中,通過重載 visit 方法實現編譯期綁定第二次分派。

優缺點深度分析

優點

  • 單一職責:元素類專注資料結構,訪問者專注操作邏輯
  • 開閉原則:新增操作只需新增 Visitor 實現,無需修改元素類
  • 複用性:同一訪問者可處理多種元素的相似操作(如統一導出、序列化)
  • 聚合操作:便於實現跨元素的整體操作(如總價、報表生成)

缺點

  • 新增元素困難:每新增一個 Element,所有 Visitor 介面與實現都要新增 visit 方法
  • 破壞封裝:訪問者需訪問元素內部狀態,可能暴露細節
  • 複雜度:引入額外介面與類,簡單場景屬於過度設計
  • 循環引用風險:ObjectStructure 持有 Element,Element 調用 Visitor,Visitor 可能回訪 ObjectStructure

適用場景判斷

適用 ✓不適用 ✗
元素類型穩定,操作頻繁變化/擴展元素類型頻繁新增
需要對物件結構進行多種不相關操作操作簡單且固定
操作邏輯複雜,需分離維護物件結構本身極其簡單
編譯器/解析器 AST 遍歷高效能熱點路徑(雙分派有開銷)

實戰最佳實踐

1. 結合適配器模式簡化訪問者實現

// 預設空實現適配器
public class ShoppingCartVisitorAdapter implements ShoppingCartVisitor {
    public int visit(Book book) { return 0; }
    public int visit(Fruit fruit) { return 0; }
}

// 只關心書籍的訪問者
public class BookOnlyVisitor extends ShoppingCartVisitorAdapter {
    @Override
    public int visit(Book book) {
        return book.getPrice(); // 忽略水果
    }
}

2. Java 8+ default 方法簡化介面


public interface ShoppingCartVisitor {
    default int visit(Book book) { return 0; }
    default int visit(Fruit fruit) { return 0; }
    // 新增元素時不破壞現有實現
}

3. 針對複雜物件結構的遍歷封裝


public class ShoppingCart {
    private List items = new ArrayList<>();
    
    public void accept(ShoppingCartVisitor visitor) {
        for (ItemElement item : items) {
            item.accept(visitor);
        }
    }
}

4. 訪問者攜帶狀態實現聚合計算


public class PriceCalculator implements ShoppingCartVisitor {
    private int total = 0;
    private Map categorySum = new HashMap<>();
    
    public int visit(Book book) {
        total += book.getPrice();
        categorySum.merge("Book", book.getPrice(), Integer::sum);
        return book.getPrice();
    }
    // ... getter
}

經典應用場景

  • 編譯器 AST:語法樹節點(變量、表達式、語句)接受不同訪問者(類型檢查、代碼生成、優化、pretty-print)
  • 文檔導出:文檔元素(段落、表格、圖片)接受導出訪問者(PDF、HTML、Word、Markdown)
  • 圖形編輯器:圖形(圓、矩形、線)接受操作訪問者(繪製、面積計算、序列化、碰撞檢測)
  • 報表引擎:數據行接受格式化訪問者(求和、平均、分組、透視表)

重構建議

「大多數情況下你不需要訪問者模式,但當你真正需要它時,你會知道的。」—— GoF

  • 優先考慮策略模式或命令模式處理單一操作變化
  • 若操作與資料結構強綁定,直接在元素類添加方法更簡潔
  • 僅在「元素穩定、操作多變、需跨元素聚合」三條件同時滿足時引入
  • 引入前評估維護成本:每新增元素類型 = 修改所有 Visitor
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved