訪客模式

📢 本文由 gemini-2.5-flash 翻譯

訪客模式 (Visitor Pattern) 物件行為模式

目的

表示作用於某個物件結構中各個元素的操作。它允許在不改變各元素類別的前提下,定義作用於這些元素的新操作。

結構

访问者模式

其中:

  • Visitor (訪客) 為該物件結構中 ConcreteElement 的每一個類別宣告一個 Visit 操作。該操作的名稱和特殊識別碼標明了發送 Visit 請求給該訪客的那個類別,這使得訪客可以確定正在被拜訪元素的具體類別。如此訪客就能透過該元素的特定介面直接存取它。
  • ConcreteVisitor (具體訪客) 實作每個由 Visitor 宣告的操作,每個操作實作本演算法的一部分,而該演算法片段則是對應於結構中物件的類別。ConcreteVisitor 為該演算法提供了上下文並儲存其局部狀態。這個狀態通常在遍歷該結構的過程中累積。
  • Element (元素) 定義以一個訪客為參數的 Accept 操作。
  • ConcreteElement (具體元素) 實作以一個訪客為參數的 Accept 操作。
  • ObjectStructure (物件結構) 能枚舉其元素;可以提供一個高階介面以允許該訪客存取其元素;可以是一個組合 (Composite) 或一個集合 (Collection),例如一個列表 (List) 或一個無序集合 (Set)。

適用性

訪客模式適用於:

  • 一個物件結構包含許多類別物件,它們有不同的介面,而使用者想要對這些物件執行一些依賴於其具體類別的操作。
  • 需要對一個物件結構中的物件執行許多不同且不相關的操作,同時又希望避免這些操作「污染」這些物件的類別。訪客模式讓使用者能將相關操作集中定義在一個類別中。當該物件結構被許多應用程式共用時,使用訪客模式可讓每個應用程式只包含所需的操作。
  • 定義物件結構的類別很少改變,但經常需要在此結構上定義新的操作。改變物件結構的類別需要重新定義對所有訪客的介面,這可能需要很大的代價。如果物件結構的類別經常改變,那麼或許還是在這些類別中定義這些操作會比較好。

範例 1

某圖書管理系統中管理著兩種文獻類型:圖書和論文。現在要求統計所有館藏文獻的總頁碼,採用訪客模式 (Visitor Pattern) 來實現此要求,類別圖如下:

访问者模式-例子1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
interface LibraryVisitor{
    void visit(Book p_book);
    void visit(Article p_article);
    void printSum();
}

class LibrarySumPrintVisitor implements LibraryVisitor{ // 列印總頁數
    private int sum = 0;

    @Override
    public void visit(Book p_book) {
        sum = sum + p_book.getNumberOfPages();
    }

    @Override
    public void visit(Article p_article) {
        sum = sum + p_article.getNumberOfPages();
    }

    @Override
    public void printSum() {
        System.out.println("SUM = " + sum);
    }
}

interface LibraryItemInterface{
    void accept(LibraryVisitor visitor);
}

class Article implements LibraryItemInterface{
    private String m_title; // 論文名稱
    private String m_author; // 論文作者
    private int m_start_page;
    private int m_end_page;
    
    public Article(String p_author, String p_title, int p_start_page, int p_end_page){
        m_title = p_title;
        m_author = p_author;
        m_start_page = p_start_page;
        m_end_page = p_end_page;
    }
    
    public int getNumberOfPages(){
        return m_end_page - m_start_page;
    }

    @Override
    public void accept(LibraryVisitor visitor) {
        visitor.visit(this);
    }
}

class Book implements LibraryItemInterface{
    private String m_title; // 書名
    private String m_author; // 書作者
    private int m_pages; // 頁數

    public Book(String p_author, String p_title, int p_pages){
        m_title = p_title;
        m_author = p_author;
        m_pages = p_pages;
    }

    public int getNumberOfPages(){
        return m_pages;
    }

    @Override
    public void accept(LibraryVisitor visitor) {
        visitor.visit(this);
    }
}

範例 2

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import java.util.ArrayList;
import java.util.List;

public class VisitorPattern {
    public static void main(String[] args) {
        PersonStructure personStructure = new PersonStructure();

        Visitor1 visitor1 = new Visitor1();
        System.out.println("For Visitor1");
        personStructure.Accept(visitor1);
        System.out.println("The sum of student age: " + visitor1.getStudentAgeSum());
        System.out.println("The sum of teacher age: " + visitor1.getTeacherAgeSum());

        Visitor2 visitor2 = new Visitor2();
        System.out.println("For Visitor2");
        personStructure.Accept(visitor2);
        System.out.println("Max score: " + visitor2.getMaxScore());
        System.out.println("Max work year: " + visitor2.getMaxWorkYear());

    }
}

interface Visitor{
    public void VisitS(Student student);
    public void VisitT(Teacher teacher);
}

class Visitor1 implements Visitor{// 分別統計學生和老師的年齡總和
    private int studentAgeSum = 0;
    private int teacherAgeSum = 0;

    public int getStudentAgeSum() {
        return studentAgeSum;
    }

    public int getTeacherAgeSum() {
        return teacherAgeSum;
    }

    @Override
    public void VisitS(Student student){
        System.out.println("Visitor1: " + student.getName() + " Age: " + student.getAge());
        studentAgeSum += student.getAge();
    }
    public void VisitT(Teacher teacher){
        System.out.println("Visitor1: " + teacher.getName() + " Age: " + teacher.getAge());
        teacherAgeSum += teacher.getAge();
    }
}

class Visitor2 implements Visitor{ // 分別找出學生最高成績以及老師最高資歷年資
    private int maxScore = -1;
    private int maxWorkYear = -1;

    public int getMaxScore() {
        return maxScore;
    }

    public int getMaxWorkYear() {
        return maxWorkYear;
    }

    @Override
    public void VisitS(Student student){
        System.out.println("Visitor2: " + student.getName() + " Score: " + student.getScore());
        if(student.getScore() > maxScore) maxScore = student.getScore();
        // maxScore = Math.max(maxScore, student.getScore());
    }
    public void VisitT(Teacher teacher){
        System.out.println("Visitor2: " + teacher.getName() + " WorkYear: " + teacher.getWorkYear());
        if(teacher.getWorkYear() > maxWorkYear) maxWorkYear = teacher.getWorkYear();
        // maxWorkYear = Math.max(maxWorkYear, teacher.getWorkYear());
    }
}

class PersonStructure{
    private List<Person> personList = new ArrayList<>();

    public PersonStructure(){
        personList.add(new Student("Mike", 16, 99));
        personList.add(new Student("Jane", 15, 100));

        personList.add(new Teacher("Alice mana", 20, 1));
    }
    public void Accept(Visitor visitor){
        for(Person p : personList){
            p.Accept(visitor);
        }
    }
}

abstract class Person{
    private String name;
    private int age;

    public abstract void Accept(Visitor visitor);

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

class Student extends Person{
    private int score;

    public int getScore() {
        return score;
    }

    public Student(String name, int age, int score){
        this.setName(name);
        this.setAge(age);
        this.score = score;
    }
    @Override
    public void Accept(Visitor visitor){
        visitor.VisitS(this);
    }
}

class Teacher extends Person{
    private int workYear;

    public int getWorkYear() {
        return workYear;
    }

    public Teacher(String name, int age, int workYear){
        this.setName(name);
        this.setAge(age);
        this.workYear = workYear;
    }
    @Override
    public void Accept(Visitor visitor){
        visitor.VisitT(this);
    }
}