訪問者模式 Visitor Pattern

1203 字
6 分鐘
訪問者模式 Visitor Pattern

目的#

定義能逐一施行於物件結構裡各個元素的操作,讓你不必修改作用對象的類別介面,就能定義新的操作;將操作集中到物件結構上,以讓它們能獨立變化,但仍以多型方式進行

適用#

  • 物件結構含有許多介面各異的類別,希望能依據物件的實體類別來執行對應的動作時。需要對整個物件結構體系執行各種互不相干的操作,不會造成干擾。物件結構的類別不常變動,但施於其上的操作卻常有增減時。

結構及成員#

Collaborations: Client先建立一個ConcreteVisitor物件,再於巡訪物件結構的過程中將Visitor物件餵給每一個遇到的ElementElement被巡訪到時,會呼叫Visitor相對應的操作,如有必要,也會把自己列入參數傳送過去讓Visitor存取。

  • Visitor: like the NodeVisitor替每一種ConcreteElement制訂對應的Visit()方法。Visitor藉由方法的名字和簽名(Sinature)(多型)區分是哪個ConcreteElement送過來的訊息。將訊息轉傳給ConcreteVisitor。ConcreteVisitor: like the TypeCheckingVisitor, SpellingVisitor具體實作出Visitor。演算法可從ConcreteVisitor得知目前的Context,在整個巡訪過程中均可在此儲存局部狀態以累積資訊。Element: like the Glyph, Node宣告以Visitor物件當參數的Accept()介面。ConcreteElement: like the Row, Column, Text, Image, etc.具體實作出Accept()方法。Client:可巡訪整群Element物件。可提供高階介面讓Visitor巡訪Element。可以是Composite,也可以是容器(List、Set)。

影響結果#

好處#

  • 容易增添新的操作,新增一個Visitor即可新增操作。每一種Visitor都將相關操作集中起來,將無關的摒除在外。處理不同的類別階層, Iterator只能巡訪Item或Item的子類別,而Visitor只要把相關類別寫入方法,就沒有巡訪限制。累積狀態。Visitor可在拜訪物件結構各個元素時順便累積狀態。

壞處#

  • 難以增添新的ConcreteElement類別。Visitor可能會迫使ConcreteElement公開一些可存取的Element內部狀態,破壞封裝。

實作#

雙重分派(Double Dispatch)?#

  • 在單一分派程式語言,呼叫哪個Overload方法,由方法呼叫者和方法參數共同在執行期間依靜態類別決定。如:以下程式碼所有的ride()出來都會是「騎馬」,因為他們的靜態類別都是Horse。
public class Mozi {
public void ride(Horse h){ System.out.println("騎馬"); }
public void ride(WhiteHorse wh){ System.out.println("騎白馬"); }
public void ride(BlackHorse bh){ System.out.println("騎黑馬"); }
public static void main(String[] args) {
Horse wh = new WhiteHorse();
Horse bh = new BlackHorse();
Mozi mozi = new Mozi();
mozi.ride(wh);
mozi.ride(bh);
}
}

但JAVA可以透過Override實作動態分派

public class Horse {
public void ride(){ System.out.println("騎馬"); }
}
public class WhiteHorse extends Horse {
public void ride(){ System.out.println("騎白馬"); }
}
public class BlackHorse extends Horse {
public void ride(){ System.out.println("騎黑馬"); }
}
public class Mozi {
public static void main(String[] args) {
Horse wh = new WhiteHorse();
Horse bh = new BlackHorse();
wh.ride();
bh.ride();
}
}
  • Visitor模式將真正會啟用的操作,依Visitor的型別和作用對象的型別決定,不用把靜態操作繫結在Element介面裡。

誰負責巡訪物件結構?#

有三種角色可以幫Visitor一一拜訪物件結構裡的每一個元素:物件結構、Visitor物件、Iterator物件。

Example: Liquor#

//Interface: Visitable
package liquor;
interface Visitable {
public double accept(Visitor visitor);
}
//Class: Liquor
package liquor;
class Liquor implements Visitable {
private double price;
Liquor(double item) {
price = item;
}
public double accept(Visitor visitor) {
return visitor.visit(this);
}
public double getPrice() {
return price;
}
}
//Class: Necessity
package liquor;
class Necessity implements Visitable {
private double price;
Necessity(double item) {
price = item;
}
public double accept(Visitor visitor) {
return visitor.visit(this);
}
public double getPrice() {
return price;
}
}
//Class: Tobaco
package liquor;
class Tobacco implements Visitable {
private double price;
Tobacco(double item) {
price = item;
}
public double accept(Visitor visitor) {
return visitor.visit(this);
}
public double getPrice() {
return price;
}
}
//Interface: Visitor
package liquor;
interface Visitor {
public double visit(Liquor liquorItem);
public double visit(Tobacco tobaccoItem);
public double visit(Necessity necessityItem);
}
//Class: TaxVisitor
package liquor;
import java.text.DecimalFormat;
class TaxVisitor implements Visitor {
DecimalFormat df = new DecimalFormat("#.##");
public TaxVisitor() {
}
public double visit(Liquor liquorItem) {
System.out.println("Liquor Item: Price with Tax");
return Double.parseDouble(df.format((liquorItem.getPrice() * .18) + liquorItem.getPrice()));
}
public double visit(Tobacco tobaccoItem) {
System.out.println("Tobacco Item: Price with Tax");
return Double.parseDouble(df.format((tobaccoItem.getPrice() * .32) + tobaccoItem.getPrice()));
}
public double visit(Necessity necessityItem) {
System.out.println("Necessity Item: Price with Tax");
return Double.parseDouble(df.format(necessityItem.getPrice()));
}
}
//Class: TaxHolidayVisitor
package liquor;
import java.text.DecimalFormat;
class TaxHolidayVisitor implements Visitor {
DecimalFormat df = new DecimalFormat("#.##");
public TaxHolidayVisitor() {
}
public double visit(Liquor liquorItem) {
System.out.println("Liquor Item: Price with Tax");
return Double.parseDouble(df.format((liquorItem.getPrice() * .10) + liquorItem.getPrice()));
}
public double visit(Tobacco tobaccoItem) {
System.out.println("Tobacco Item: Price with Tax");
return Double.parseDouble(df.format((tobaccoItem.getPrice() * .30) + tobaccoItem.getPrice()));
}
public double visit(Necessity necessityItem) {
System.out.println("Necessity Item: Price with Tax");
return Double.parseDouble(df.format(necessityItem.getPrice()));
}
}
//Class: VisitorTest
package liquor;
public class VisitorTest {
public static void main(String[] args) {
TaxVisitor taxCalc = new TaxVisitor();
TaxHolidayVisitor taxHolidayCalc = new TaxHolidayVisitor();
Necessity milk = new Necessity(3.47);
Liquor vodka = new Liquor(11.99);
Tobacco cigars = new Tobacco(19.99);
System.out.println(milk.accept(taxCalc) + "\n");
System.out.println(vodka.accept(taxCalc) + "\n");
System.out.println(cigars.accept(taxCalc) + "\n");
System.out.println("TAX HOLIDAY PRICES\n");
System.out.println(milk.accept(taxHolidayCalc) + "\n");
System.out.println(vodka.accept(taxHolidayCalc) + "\n");
System.out.println(cigars.accept(taxHolidayCalc) + "\n");
}
}
Result:
Necessity Item: Price with Tax
3.47
Liquor Item: Price with Tax
14.15
Tobacco Item: Price with Tax
26.39
TAX HOLIDAY PRICES
Necessity Item: Price with Tax
3.47
Liquor Item: Price with Tax
13.19
Tobacco Item: Price with Tax
25.99

文章分享

如果這篇文章對你有幫助,歡迎分享給更多人!

訪問者模式 Visitor Pattern
https://linziyou.info/posts/2020-10-13-訪問者模式-visitor-pattern/
作者
Lin Ziyou
發布於
2020-10-13
許可協議
CC BY-NC-SA 4.0
最後更新於 2020-10-13,距今已過 1966 天

部分內容可能已過時

Profile Image of the Author
Lin Ziyou
Hi! I'm Jerry~
分類
標籤
站點統計
文章
45
分類
8
標籤
10
總字數
43,470
運作天數
0
最後活動
0 天前

目錄