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

PHP 設計模式精講:訪問者模式實現數據結構與操作解耦

     編輯:PHP 綜合知識

問題背景

計算機組件(CPU、內存、鍵盤)需要支持多種操作:打印名稱、保存數據庫、顯示價格... 若在基類不斷添加方法,違反開閉原則,類會無限膨脹。

訪問者模式核心思想

將數據結構(元素)與作用於其上的操作(訪問者)分離,使操作集合可自由演化而不修改元素類。

代碼實現

1. 元素接口與具體元素

abstract class Unit {
    abstract public function getName();
    
    // 關鍵:接受訪問者,回調訪問者對應方法
    public function accept(Visitor $visitor) {
        $method = 'visit' . get_class($this); // visitCpu, visitMemory...
        if (method_exists($visitor, $method)) {
            $visitor->$method($this);
        }
    }
}

class Cpu extends Unit {
    public function getName() { return 'i am cpu'; }
}
class Memory extends Unit {
    public function getName() { return 'i am memory'; }
}
class Keyboard extends Unit {
    public function getName() { return 'i am keyboard'; }
}

2. 訪問者接口與具體訪問者

interface Visitor {
    public function visitCpu(Cpu $cpu);
    public function visitMemory(Memory $memory);
    public function visitKeyboard(Keyboard $keyboard);
}

// 打印訪問者
class PrintVisitor implements Visitor {
    public function visitCpu(Cpu $cpu) { echo "hello, " . $cpu->getName() . "\n"; }
    public function visitMemory(Memory $m) { echo "hello, " . $m->getName() . "\n"; }
    public function visitKeyboard(Keyboard $k) { echo "hello, " . $k->getName() . "\n"; }
}

// 數據庫保存訪問者(新增操作無需修改元素類)
class SaveVisitor implements Visitor {
    public function visitCpu(Cpu $cpu) { /* save cpu */ }
    public function visitMemory(Memory $m) { /* save memory */ }
    public function visitKeyboard(Keyboard $k) { /* save keyboard */ }
}

// 價格訪問者
class PriceVisitor implements Visitor {
    public function visitCpu(Cpu $cpu) { echo "CPU: $299\n"; }
    public function visitMemory(Memory $m) { echo "Memory: $129\n"; }
    public function visitKeyboard(Keyboard $k) { echo "Keyboard: $79\n"; }
}

3. 對象結構

class Computer {
    private $_items = [];
    
    public function add(Unit $unit) { $this->_items[] = $unit; }
    
    public function accept(Visitor $visitor) {
        foreach ($this->_items as $item) {
            $item->accept($visitor); // 雙分派核心
        }
    }
}

4. 客戶端調用

$computer = new Computer();
$computer->add(new Cpu());
$computer->add(new Memory());
$computer->add(new Keyboard());

// 打印
$computer->accept(new PrintVisitor());
// hello, i am cpu
// hello, i am memory
// hello, i am keyboard

// 保存
$computer->accept(new SaveVisitor());

// 顯示價格
$computer->accept(new PriceVisitor());

雙分派機制解析

Client → Computer.accept(Visitor)
           ↓
       Unit.accept(Visitor)  ← 第一次分派:根據元素類型
           ↓
   Visitor.visitCpu(Cpu)     ← 第二次分派:根據訪問者類型
           ↓
       具體業務邏輯

優缺點總結

優點缺點
新增操作只需新增 Visitor,不修改元素類新增元素類需修改所有 Visitor 接口及實現
符合單一職責、開閉原則破壞封裝(訪問者需訪問元素內部狀態)
相關操作集中在一個 Visitor 中適用於元素結構穩定、操作頻繁變化的場景

適用場景

  • 對象結構中對象類型較少且穩定(如 AST 節點、文檔元素、編譯器 IR)
  • 需要對結構進行多種不同操作(導出、分析、優化、序列化)
  • 操作邏輯複雜,不宜放入元素類內部

PHP 實戰建議

  • 利用 method_exists() 實現動態分派,減少接口約束
  • 配合 IteratorAggregate 遍歷複雜結構
  • 大型項目考慮使用註解或屬性自動註冊訪問者
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved