設計模式,行爲模式之訪問者模式

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結尾的類,都運用了訪問者模式,如FileVisitorAnnotationValueVisitor

4 總結

訪問者模式可以讓對象與算法邏輯分離,使程序更易於修改和擴展。當然,缺點是訪問者接口的實現過多,會使得訪問者變得很龐雜。雖然這種模式在實際中不多見,但在合適的場景中,恰當地運用可以有效降低系統複雜度。

文中例子的github地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章