訪問者設計模式是一種行為模式,允許您向現有對象結構添加新作,而無需修改其類。
它通過允許您將算法與其作的對象分開來實現這一點。
它在以下情況下特別有用:
您有一個復雜的對象結構(如 AST、文檔或 UI 元素),您希望對其執行多個不相關的作。
您希望 在不更改其源代碼的情況下向類添加新行為。
您需要根據對象的具體類型執行不同的作,而不是訴諸長鏈 或 檢查。if-elseinstanceof
通過訪問者模式, 您可以將作外部化為單獨訪問者客類。每個訪問者都會為每種元素類型實現行為,而元素只是接受訪問者。這可以保持數據結構的整潔,邏輯模塊化和可擴展性。
讓我們通過一個真實世界的示例來了解如何應用訪問者模式來將行為與結構清晰地分離,并使我們的系統更容易擴展,而無需觸及現有類。
問題:向形狀層次結構添加作
想象一下,您正在構建一個 支持多種形狀類型的矢量圖形編輯器:
Circle
Rectangle
Triangle
每個形狀都是公共層次結構的一部分,必須支持各種作,例如:
在屏幕上渲染
計算面積
導出為 SVG
序列化為 JSON
最簡單的方法是將所有這些方法添加到每個形狀類:
public?interface?Shape?{void?accept(ShapeVisitor?visitor);
}
public?class?Circle?implements?Shape?{private?final?double?radius;public?Circle(double?radius)?{this.radius?=?radius;}public?double?getRadius()?{return?radius;}@Overridepublic?void?accept(ShapeVisitor?visitor)?{visitor.visitCircle(this);}
}
為什么這會崩潰
此解決方案對于幾個作似乎很好,但隨著添加新的作或形狀類型,很快就會出現問題。
- 1. 違反單一責任原則
每個形狀類現在包含多個不相關的職責:
幾何計算
繪圖
序列化
導出格式
可能的印刷、造型等。
這會使班級膨脹并使其更難維護。 - 2. 難以擴展
如果您需要添加新作(例如 ),則必須:generatePdf()
修改層次結構中的每個類
重新編譯所有內容
可能會破壞現有邏輯
這違反了開放/封閉原則——類應該開放以進行擴展,但關閉以進行修改。 - 3. 你并不總是控制班級
如果形狀類是第三方庫或生成的代碼的一部分,該怎么辦?您無法輕松直接添加新行為。
我們真正需要什么
我們需要一個解決方案,讓我們能夠:
將作與形狀類分開
添加新行為而不修改現有類
避免重復檢查或使用類型開關來處理不同的形狀instanceof
這正是訪問者設計模式旨在解決的問題。
訪問者模式
通過訪問者設計模式, 您可以將算法與其作的對象分開。它使您能夠將新作添加到類層次結構中,而無需修改類本身。
當您有以下情況時,這尤其有用:
一組穩定的元素類(例如形狀)
需要 跨這些類工作的一組作(例如,渲染、導出、計算)
類圖
?
- 1. 元素接口(例如Shape)
表示對象結構中的對象(例如圖形形狀、文檔節點、AST 元素)。
聲明單個方法:
void accept(Visitor visitor);
每個想要訪問的類都必須實現此接口。
這允許訪問者被“接受”到對象中,以便它可以對其執行作。
- 2. 具體元素(例如, CircleRectangle)
實現 接口。Element
在方法中 ,他們使用 調用訪問者的相應方法 。accept()visitor.visitX(this) - 3. 訪問者界面
聲明一組 方法 — 每個具體元素類型一個。visit()
每種方法都是為處理特定類型的元素而定制的。
此接口允許您定義應用于模型中各種元素的外部作。 - 4. 混凝土訪問者(例如AreaCalculatorVisitor)
實現 接口。Visitor
每個訪問者都代表需要 跨元素執行的特定作。
實現應用于元素的特定行為(例如,導出、驗證、轉換)
實現訪問者模式
讓我們使用 Visitor Pattern 重構具有多個形狀 (, ) 的圖形系統來執行兩個作:CircleRectangle
計算 每個形狀的面積
將形狀導出為 SVG 格式
- 1. 定義 接口(元素)Shape
所有形狀都必須接受訪問者。
public?interface?Shape?{void?accept(ShapeVisitor?visitor);
}
- 2. 創建具體形狀類(元素)
每個形狀類實現 并委托給訪問者。accept()
🔵 圈
public?class?Circle?implements?Shape?{private?final?double?radius;public?Circle(double?radius)?{this.radius?=?radius;}public?double?getRadius()?{return?radius;}@Overridepublic?void?accept(ShapeVisitor?visitor)?{visitor.visitCircle(this);}
}
🟥 矩形
public?class?Rectangle?implements?Shape?{private?final?double?width;private?final?double?height;public?Rectangle(double?width,?double?height)?{this.width?=?width;this.height?=?height;}public?double?getWidth()?{return?width;}public?double?getHeight()?{return?height;}@Overridepublic?void?accept(ShapeVisitor?visitor)?{visitor.visitRectangle(this);}
}
- 3. 定義訪問者界面
每種方法對應于一種形狀類型。
public?interface?ShapeVisitor?{void?visitCircle(Circle?circle);void?visitRectangle(Rectangle?rectangle);
}
- 4. 實施具體訪問者
📏 面積計算器訪問者
public?class?AreaCalculatorVisitor?implements?ShapeVisitor?{@Overridepublic?void?visitCircle(Circle?circle)?{double?area?=?Math.PI?*?circle.getRadius()?*?circle.getRadius();System.out.println("Area?of?Circle:?"?+?area);}@Overridepublic?void?visitRectangle(Rectangle?rectangle)?{double?area?=?rectangle.getWidth()?*?rectangle.getHeight();System.out.println("Area?of?Rectangle:?"?+?area);}
}
🖼? SVG 導出器訪問者
public?class?SvgExporterVisitor?implements?ShapeVisitor?{@Overridepublic?void?visitCircle(Circle?circle)?{System.out.println("<circle?r=\""?+?circle.getRadius()?+?"\"?/>");}@Overridepublic?void?visitRectangle(Rectangle?rectangle)?{System.out.println("<rect?width=\""?+?rectangle.getWidth()?+"\"?height=\""?+?rectangle.getHeight()?+?"\"?/>");}
}
- 5. 客戶端代碼
現在,您可以使用任何訪問器對形狀結構進行作。
public?class?VisitorPatternDemo?{public?static?void?main(String[]?args)?{List<Shape>?shapes?=?List.of(new?Circle(5),new?Rectangle(10,?4),new?Circle(2.5));System.out.println("===?Calculating?Areas?===");ShapeVisitor?areaCalculator?=?new?AreaCalculatorVisitor();for?(Shape?shape?:?shapes)?{shape.accept(areaCalculator);}System.out.println("\n===?Exporting?to?SVG?===");ShapeVisitor?svgExporter?=?new?SvgExporterVisitor();for?(Shape?shape?:?shapes)?{shape.accept(svgExporter);}}
}
輸出
===?Calculating?Areas?===
Area?of?Circle:?78.53981633974483
Area?of?Rectangle:?40.0
Area?of?Circle:?19.634954084936208===?Exporting?to?SVG?===
<circle?r="5.0"?/>
<rect?width="10.0"?height="4.0"?/>
<circle?r="2.5"?/>
我們取得了什么成就
解耦邏輯:形狀類是干凈的;邏輯存在于訪問者中
開放/關閉原則:輕松添加新訪問者(例如,無需接觸形狀)JsonExporterVisitor
雙重調度:無需進行類型檢查或進行類型檢查instanceof
可重用性和可維護性:每個訪問者專注于一項作,并且可以單獨測試