1 概述
訪問者模式(Visitor Pattern)是一種行爲模式,不常用。它可以將作用在對象上的算法邏輯,與對象本身分離開來。
2 訪問者模式
當需要對一組相似類型的對象執行操作時,我們可以將操作邏輯分別維護在每個對象內部,但這違背了單一職責原則。
訪問者模式就是來應對這種情況的:將所有的算法邏輯移動到一個新的類----訪問者(Visitor)中,統一維護,如果其中的邏輯發生了變化,那麼我們只需要在訪問者實現中進行更改,而不用影響到原對象。同時,在訪問者模式中,擴展變得很容易,增加新的對象以及操作邏輯,只需要在訪問者中做添加即可。
3 案例
來看一個例子。購物結算時,需要統計所有商品的價格,同時還要考慮到商品的折扣,不同的商品,優惠政策也不一樣。我們用訪問者模式,來統一處理結算的邏輯:
public interface Visitable {
int accept(Visitor visitor);
}
public interface Fruit extends Visitable {
int getPricePerKg();
int getWeight();
}
// 水果類只需要維護自身的屬性如單價,重量等信息,無需關心結算方式
public class Apple implements Fruit {
private int pricePerKg;
private int weight;
public Apple(int pricePerKg, int weight) {
this.pricePerKg = pricePerKg;
this.weight = weight;
}
@Override
public int getPricePerKg() {
return pricePerKg;
}
@Override
public int getWeight() {
return weight;
}
@Override
public int accept(Visitor visitor) {
return visitor.visit(this);
}
}
public class Orange implements Fruit {
private int pricePerKg;
private int weight;
public Orange(int pricePerKg, int weight) {
this.pricePerKg = pricePerKg;
this.weight = weight;
}
@Override
public int getPricePerKg() {
return pricePerKg;
}
@Override
public int getWeight() {
return weight;
}
@Override
public int accept(Visitor visitor) {
return visitor.visit(this);
}
}
public class Banana implements Fruit {
private int pricePerKg;
private int weight;
public Banana(int pricePerKg, int weight) {
this.pricePerKg = pricePerKg;
this.weight = weight;
}
@Override
public int getPricePerKg() {
return pricePerKg;
}
@Override
public int getWeight() {
return weight;
}
@Override
public int accept(Visitor visitor) {
return visitor.visit(this);
}
}
public interface Visitor {
int getTotalCost(Fruit... fruits);
int visit(Apple apple);
int visit(Orange orange);
int visit(Banana banana);
}
// 訪問者類維護具體的算法邏輯
public class FruitVisitor implements Visitor {
@Override
public int getTotalCost(Fruit... fruits) {
int cost = 0;
for (Fruit fruit : fruits) {
cost += fruit.accept(this);
}
return cost;
}
@Override
public int visit(Apple apple) {
// 蘋果打八折
int pricePerKg = apple.getPricePerKg();
if (pricePerKg > 10) {
pricePerKg *= 0.8;
}
int cost = pricePerKg * apple.getWeight();
System.out.println(apple.getWeight() + "kg apples costs $" + cost);
return cost;
}
@Override
public int visit(Orange orange) {
// 橘子滿2千克,單價減2元
int pricePerKg = orange.getPricePerKg();
if (orange.getWeight() > 2) {
pricePerKg -= pricePerKg - 2;
}
int cost = pricePerKg * orange.getWeight();
System.out.println(orange.getWeight() + "kg oranges costs $" + cost);
return cost;
}
@Override
public int visit(Banana banana) {
// 香蕉沒有折扣
int cost = banana.getPricePerKg() * banana.getWeight();
System.out.println(banana.getWeight() + "kg bananas costs $" + cost);
return cost;
}
}
輸出:
1kg apples costs $9
3kg oranges costs $6
2kg bananas costs $16
Total cost: 31
通過將getTotalCost()
的邏輯從Fruit
中抽離出來,放到獨立的類Visitor
中單獨維護,使得代碼滿足了單一職責原則。對於折扣算法的修改,都不會影響到原有的Fruit
對象,達到了對象與算法解耦的目的。
JDK
中,一般以Visitor
結尾的類,都運用了訪問者模式,如FileVisitor,AnnotationValueVisitor…
4 總結
訪問者模式可以讓對象與算法邏輯分離,使程序更易於修改和擴展。當然,缺點是訪問者接口的實現過多,會使得訪問者變得很龐雜。雖然這種模式在實際中不多見,但在合適的場景中,恰當地運用可以有效降低系統複雜度。