初識設計模式

2022-11-10 12:02:13

簡介

存取者設計模式(Visitor Design Pattern)的定義是,允許一個或多個操作應用到一組物件上,解耦操作和物件本身。

在使用存取者模式的時候,被存取的元素通常不是單獨存在的,它們儲存在一個集合中,這個集合稱為「物件結構」,存取者通過遍歷物件結構實現對其儲存的元素進行逐個存取。

存取者模式使用了「雙重分派」的呼叫機制,即元素物件定義一個操作方法支援注入存取者物件,在操作方法內呼叫存取者的存取方法,並將當前元素物件傳入到存取方法中。

具體實現

在這裡舉一個工作當中的具體例子,在小公司的專案組當中,名義上區分了開發、測試等崗位,但實際上開發人員既要會開發,也有會測試,對於測試人員也是同樣的要求,既要會測試,也要會開發。

在這裡案例當中,開發人員、測試人員統稱為元素,我們在這裡先構建一個抽象的元素類。其程式碼範例如下:

public interface Element {
    // 定義一個接受存取者存取的抽象方法
    void accept(Visitor visitor);
}

對於開發人員類,根據自己的情況實現這個 accept() 方法,其程式碼如下:

public class Programmer implements Element {
    private String name = "開發人員";

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visitProgrammer(this);
    }
}

對於測試人員,根據自己的情況實現這個 accept() 方法,其程式碼如下:

public class Tester implements Element {
    private String name = "測試人員";

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visitTester(this);
    }
}

第二步,最重要的就是要定義好一個存取者類,在這裡抽象出的存取者介面可以是以專案組為範圍,為專案組中的每一個元素定義對應的存取方法。其程式碼範例如下:

public interface Visitor {
    void visitProgrammer(Programmer programmer);

    void visitTester(Tester tester);
}

每當出現一個新的操作時,就可以實現存取者介面,注入不同的元素物件以實現不同的操作。

如下是開發人員和測試人員使用開發技能的程式碼範例:

public class DevelopVisitor implements Visitor {
    @Override
    public void visitProgrammer(Programmer programmer) {
        System.out.println(programmer.getName() + "在開發");
    }

    @Override
    public void visitTester(Tester tester) {
        System.out.println(tester.getName() + "在開發");
    }
}

如下是開發人員和測試人員使用測試技能的程式碼範例:

public class TestVisitor implements Visitor {
    @Override
    public void visitProgrammer(Programmer programmer) {
        System.out.println(programmer.getName() + "在測試");
    }

    @Override
    public void visitTester(Tester tester) {
        System.out.println(tester.getName() + "在測試");
    }
}

總結

優點

存取者模式的主要優點如下:

  • 能夠在不修改物件結構中的元素的情況下,為物件結構中的元素新增新的功能,符合開閉原則
  • 將有關元素的行為都封裝到一個存取者物件中,每個存取者物件的功能都比較單一,符合單一職責原則

缺點

存取者模式的主要缺點如下:

  • 增加新的元素類需要在每一個存取者類中都增加相應的具體操作,這違背了開閉原則
  • 存取者物件可以存取並呼叫每一個元素物件的操作,這意味著元素物件有時候會暴露一些內部操作和內部狀態,破壞了封裝
  • 存取者模式依賴了具體類,而沒有依賴抽象類,違反了依賴倒置原則

適用場景

存取者模式的適用場景如下:

  • 物件結構中元素物件的類很少改變,但經常需要在此物件結構上定義新的操作

原始碼

存取者模式提供一個方便的可維護的方式來操作一組物件,JDK 內建了這樣的元素介面和存取者介面。

如下是元素介面 javax.lang.model.element.Element 的部分程式碼:

public interface Element extends javax.lang.model.AnnotatedConstruct {
    <R, P> R accept(ElementVisitor<R, P> v, P p);
}

如下是存取者介面 javax.lang.model.element.ElementVisitor 的部分程式碼:

public interface ElementVisitor<R, P> {
    R visit(Element e, P p);

    default R visit(Element e) {
        return visit(e, null);
    }
}