想像你是一位探險家,來到了一座神秘的古城。這座城市有著各式各樣的建築:宏偉的宮殿、莊嚴的神廟、繁忙的市集。作為一個訪問者,你想要深入了解每個地方,但每個地方都有其獨特的探索方式。這就像程式世界中的訪問者模式,一種讓你能夠優雅地處理複雜物件結構的設計模式。準備好開始這段奇妙的探索之旅了嗎?
什麼是訪問者模式?
訪問者模式是一種行為設計模式,它允許你在不改變原有物件結構的情況下,定義對這些物件的新操作。這個模式的核心思想是將資料結構和資料操作分離。在這個模式中,訪問者會遍歷不同類型的物件,並對它們進行不同的操作。這樣的好處是可以靈活地新增行為,而不用修改原來的類別。
就像我們的探險家例子,訪問者就像是探險家,而被訪問的元素就像是城市中的各個地點。無論你去到哪裡,都能以適合該地點的方式進行探索,而不需要改變這些地點本身。
訪問者模式在繪圖軟體中的應用
讓我們透過一個繪圖軟體的例子來深入理解訪問者模式。想像我們正在開發一款繪圖軟體,裡面有各式各樣的圖形,如圓形、矩形和三角形等。這些圖形都有各自的繪圖和顯示方式,但現在我們需要新增一個功能:計算每個圖形的面積。
首先我們定義訪問者介面和圖形的基本介面,
1 | // 前向宣告 |
接著我們實現具體的圖形類,這裡的 Circle
、Rectangle
和 Triangle
類就是我們所說的”不需要修改的類”。這些類代表了我們的基本圖形,它們的主要職責是維護圖形的基本屬性。
1 | class Circle : public Shape { |
然後我們可以實現具體的訪問者,例如計算面積的訪問者,
1 | // 計算面積大小的訪問者 |
最後客戶端可以這樣使用來取得所有圖形的面積總合,
1 | int main() { |
執行上述程式碼,我們會得到以下輸出:1
Total area: 108.54
透過這種方式,我們可以輕鬆地新增新的操作(如計算周長、繪製圖形等),而不需要修改現有的 Circle
、Rectangle
和 Triangle
類。這就是訪問者模式的核心優勢:當我們需要新增新的操作時,我們只需要建立一個新的訪問者類(比如 PerimeterCalculator
或 ShapeDrawer
),而不需要改變這些基本圖形類的結構。
這種設計使得我們的圖形結構保持穩定,同時允許我們靈活地新增新的操作。例如,如果我們之後想要新增一個計算所有圖形重心的功能,我們只需要建立一個新的 CenterOfGravityCalculator
訪問者,而不需要修改任何現有的圖形類。
訪問者模式在檔案結構分析中的應用
另一個常見的應用例子就是檔案結構分析。假設你有一個複雜的檔案系統,裡面包含了檔案(File)和資料夾(Folder)。當你想要統計資料夾內所有檔案的大小,或是對特定類型的檔案進行處理時,訪問者模式就能幫上大忙。
我們有一個檔案系統,其中包含兩種類型的元素:File
和 Folder
。我們需要一個訪問者來遍歷這些元素並計算檔案的大小。
定義一個訪問者介面 Visitor
,其中包含兩個方法,分別對 File
和 Folder
類別進行操作,1
2
3
4
5
6
7
8
9
10// 前向宣告
class File;
class Folder;
// 訪問者基類
class Visitor {
public:
virtual void visit(File* file) = 0;
virtual void visit(Folder* folder) = 0;
};
定義 File
和 Folder
類別,它們都會有一個 accept
方法來接受訪問者,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// 檔案類
class File {
public:
void accept(Visitor* visitor) {
visitor->visit(this);
}
int getSize() const { return size; }
private:
int size = 100; // 假設每個檔案都有大小
};
// 資料夾類
class Folder {
public:
void accept(Visitor* visitor) {
visitor->visit(this);
}
void addFile(File* file) {
files.push_back(file);
}
const std::vector<File*>& getFiles() const { return files; }
private:
std::vector<File*> files;
};
定義具體的訪問者,這個訪問者用來計算檔案總大小,1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 計算大小的訪問者
class SizeVisitor : public Visitor {
public:
void visit(File* file) override {
totalSize += file->getSize();
}
void visit(Folder* folder) override {
for (auto file : folder->getFiles()) {
file->accept(this);
}
}
int getTotalSize() const { return totalSize; }
private:
int totalSize = 0;
};
最後在客戶端中使用訪問者來計算總大小,1
2
3
4
5
6
7
8
9
10
11int main() {
File file1, file2;
Folder folder;
folder.addFile(&file1);
folder.addFile(&file2);
SizeVisitor visitor;
folder.accept(&visitor);
std::cout << "Total size: " << visitor.getTotalSize() << std::endl;
}
透過這種方式,我們可以輕鬆地新增新的操作(如搜尋、統計、結構顯示等),而不需要修改現有的 File 和 Folder 類。這就是訪問者模式的核心優勢:當我們需要新增新的操作時,我們只需要建立一個新的訪問者類(比如 FileSearcher、FileCounter 或 StructurePrinter),而不需要改變 File 和 Folder 這些基本類的結構。
這種設計使得我們的檔案系統結構保持穩定,同時允許我們靈活地新增新的操作。例如,如果我們之後想要新增一個統計特定類型檔案數量的功能,我們只需要建立一個新的 FileTypeCounter 訪問者,而不需要修改 File 和 Folder 類。
訪問者模式的優缺點
訪問者模式就像是一把瑞士軍刀,提供了極大的靈活性。它允許我們輕鬆地新增新的操作,而無需修改現有的類結構。這不僅提高了程式碼的可維護性,還有助於遵守開閉原則。此外它還能將相關的操作集中在訪問者中,讓程式碼更加整潔和易於管理。
訪問者模式也有其缺點。它不太適合經常改變結構的系統,因為每當物件結構發生變化,你就必須更新訪問者中的方法。另外如果物件的類型數量很多,訪問者模式會變得難以維護,因為每新增一種類型都需要在訪問者中新增對應的方法。
總結
訪問者模式特別適合在物件結構穩定,但需要經常增加行為的情況下使用。透過將操作與物件分離,我們可以更輕鬆地擴展系統功能,而不會影響原有的結構。不過這個模式也有其適用範圍,過於複雜的結構變化可能會使訪問者模式的維護成本變得很高。因此選擇是否使用訪問者模式時,需要根據具體需求來權衡。