想像你是一位探險家,來到了一座神秘的古城。這座城市有著各式各樣的建築:宏偉的宮殿、莊嚴的神廟、繁忙的市集。作為一個訪問者,你想要深入了解每個地方,但每個地方都有其獨特的探索方式。這就像程式世界中的訪問者模式,一種讓你能夠優雅地處理複雜物件結構的設計模式。準備好開始這段奇妙的探索之旅了嗎?
訪問者模式是一種行為設計模式,它允許你在不改變原有物件結構的情況下,定義對這些物件的新操作。這個模式的核心思想是將資料結構和資料操作分離。在這個模式中,訪問者會遍歷不同類型的物件,並對它們進行不同的操作。這樣的好處是可以靈活地新增行為,而不用修改原來的類別。
就像我們的探險家例子,訪問者就像是探險家,而被訪問的元素就像是城市中的各個地點。無論你去到哪裡,都能以適合該地點的方式進行探索,而不需要改變這些地點本身。
讓我們透過一個繪圖軟體的例子來深入理解訪問者模式。想像我們正在開發一款繪圖軟體,裡面有各式各樣的圖形,如圓形、矩形和三角形等。這些圖形都有各自的繪圖和顯示方式,但現在我們需要新增一個功能:計算每個圖形的面積。
首先我們定義訪問者介面和圖形的基本介面,
// 前向宣告
class Circle;
class Rectangle;
class Triangle;
// 訪問者基類
class Visitor {
public:
virtual void visit(Circle* circle) = 0;
virtual void visit(Rectangle* rectangle) = 0;
virtual void visit(Triangle* triangle) = 0;
};
class Shape {
public:
virtual void accept(Visitor* visitor) = 0;
virtual ~Shape() {}
};
接著我們實現具體的圖形類,這裡的 Circle
、Rectangle
和 Triangle
類就是我們所說的"不需要修改的類"。這些類代表了我們的基本圖形,它們的主要職責是維護圖形的基本屬性。
class Circle : public Shape {
public:
Circle(double radius) : radius(radius) {}
void accept(Visitor* visitor) override {
visitor->visit(this);
}
double getRadius() const { return radius; }
private:
double radius;
};
class Rectangle : public Shape {
public:
Rectangle(double width, double height) : width(width), height(height) {}
void accept(Visitor* visitor) override {
visitor->visit(this);
}
double getWidth() const { return width; }
double getHeight() const { return height; }
private:
double width, height;
};
class Triangle : public Shape {
public:
Triangle(double base, double height) : base(base), height(height) {}
void accept(Visitor* visitor) override {
visitor->visit(this);
}
double getBase() const { return base; }
double getHeight() const { return height; }
private:
double base, height;
};
然後我們可以實現具體的訪問者,例如計算面積的訪問者,
// 計算面積大小的訪問者
class AreaCalculator : public Visitor {
public:
void visit(Circle* circle) override {
totalArea += 3.14159 * circle->getRadius() * circle->getRadius();
}
void visit(Rectangle* rectangle) override {
totalArea += rectangle->getWidth() * rectangle->getHeight();
}
void visit(Triangle* triangle) override {
totalArea += 0.5 * triangle->getBase() * triangle->getHeight();
}
double getTotalArea() const { return totalArea; }
private:
double totalArea = 0;
};
最後客戶端可以這樣使用來取得所有圖形的面積總合,
int main() {
std::vector<Shape*> shapes = {
new Circle(5),
new Rectangle(4, 6),
new Triangle(3, 4)
};
AreaCalculator areaCalculator;
for (auto shape : shapes) {
shape->accept(&areaCalculator);
}
std::cout << "Total area: " << areaCalculator.getTotalArea() << std::endl;
// 釋放記憶體
for (auto shape : shapes) {
delete shape;
}
return 0;
}
執行上述程式碼,我們會得到以下輸出:
Total area: 108.54
透過這種方式,我們可以輕鬆地新增新的操作(如計算周長、繪製圖形等),而不需要修改現有的 Circle
、Rectangle
和 Triangle
類。這就是訪問者模式的核心優勢:當我們需要新增新的操作時,我們只需要建立一個新的訪問者類(比如 PerimeterCalculator
或 ShapeDrawer
),而不需要改變這些基本圖形類的結構。
這種設計使得我們的圖形結構保持穩定,同時允許我們靈活地新增新的操作。例如,如果我們之後想要新增一個計算所有圖形重心的功能,我們只需要建立一個新的 CenterOfGravityCalculator
訪問者,而不需要修改任何現有的圖形類。
訪問者模式就像是一把瑞士軍刀,提供了極大的靈活性。它允許我們輕鬆地新增新的操作,而無需修改現有的類結構。這不僅提高了程式碼的可維護性,還有助於遵守開閉原則。此外它還能將相關的操作集中在訪問者中,讓程式碼更加整潔和易於管理。
訪問者模式也有其缺點。它不太適合經常改變結構的系統,因為每當物件結構發生變化,你就必須更新訪問者中的方法。另外如果物件的類型數量很多,訪問者模式會變得難以維護,因為每新增一種類型都需要在訪問者中新增對應的方法。
訪問者模式特別適合在物件結構穩定,但需要經常增加行為的情況下使用。透過將操作與物件分離,我們可以更輕鬆地擴展系統功能,而不會影響原有的結構。不過這個模式也有其適用範圍,過於複雜的結構變化可能會使訪問者模式的維護成本變得很高。因此選擇是否使用訪問者模式時,需要根據具體需求來權衡。
更多C++語言相關的文章,歡迎追蹤我的部落格。
https://shengyu7697.github.io/cpp-visitor-pattern/