iT邦幫忙

2024 iThome 鐵人賽

DAY 23
0
Software Development

輕鬆學習設計模式Design Pattern系列 第 23

Day 23 訪問者模式 Visitor Pattern

  • 分享至 

  • xImage
  •  

想像你是一位探險家,來到了一座神秘的古城。這座城市有著各式各樣的建築:宏偉的宮殿、莊嚴的神廟、繁忙的市集。作為一個訪問者,你想要深入了解每個地方,但每個地方都有其獨特的探索方式。這就像程式世界中的訪問者模式,一種讓你能夠優雅地處理複雜物件結構的設計模式。準備好開始這段奇妙的探索之旅了嗎?

什麼是訪問者模式?

訪問者模式是一種行為設計模式,它允許你在不改變原有物件結構的情況下,定義對這些物件的新操作。這個模式的核心思想是將資料結構和資料操作分離。在這個模式中,訪問者會遍歷不同類型的物件,並對它們進行不同的操作。這樣的好處是可以靈活地新增行為,而不用修改原來的類別。

就像我們的探險家例子,訪問者就像是探險家,而被訪問的元素就像是城市中的各個地點。無論你去到哪裡,都能以適合該地點的方式進行探索,而不需要改變這些地點本身。

訪問者模式在繪圖軟體中的應用

讓我們透過一個繪圖軟體的例子來深入理解訪問者模式。想像我們正在開發一款繪圖軟體,裡面有各式各樣的圖形,如圓形、矩形和三角形等。這些圖形都有各自的繪圖和顯示方式,但現在我們需要新增一個功能:計算每個圖形的面積。

首先我們定義訪問者介面和圖形的基本介面,

// 前向宣告
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() {}
};

接著我們實現具體的圖形類,這裡的 CircleRectangleTriangle 類就是我們所說的"不需要修改的類"。這些類代表了我們的基本圖形,它們的主要職責是維護圖形的基本屬性。

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

透過這種方式,我們可以輕鬆地新增新的操作(如計算周長、繪製圖形等),而不需要修改現有的 CircleRectangleTriangle 類。這就是訪問者模式的核心優勢:當我們需要新增新的操作時,我們只需要建立一個新的訪問者類(比如 PerimeterCalculatorShapeDrawer),而不需要改變這些基本圖形類的結構。

這種設計使得我們的圖形結構保持穩定,同時允許我們靈活地新增新的操作。例如,如果我們之後想要新增一個計算所有圖形重心的功能,我們只需要建立一個新的 CenterOfGravityCalculator 訪問者,而不需要修改任何現有的圖形類。

訪問者模式的優缺點

訪問者模式就像是一把瑞士軍刀,提供了極大的靈活性。它允許我們輕鬆地新增新的操作,而無需修改現有的類結構。這不僅提高了程式碼的可維護性,還有助於遵守開閉原則。此外它還能將相關的操作集中在訪問者中,讓程式碼更加整潔和易於管理。

訪問者模式也有其缺點。它不太適合經常改變結構的系統,因為每當物件結構發生變化,你就必須更新訪問者中的方法。另外如果物件的類型數量很多,訪問者模式會變得難以維護,因為每新增一種類型都需要在訪問者中新增對應的方法。

總結

訪問者模式特別適合在物件結構穩定,但需要經常增加行為的情況下使用。透過將操作與物件分離,我們可以更輕鬆地擴展系統功能,而不會影響原有的結構。不過這個模式也有其適用範圍,過於複雜的結構變化可能會使訪問者模式的維護成本變得很高。因此選擇是否使用訪問者模式時,需要根據具體需求來權衡。

更多C++語言相關的文章,歡迎追蹤我的部落格。
https://shengyu7697.github.io/cpp-visitor-pattern/


上一篇
Day 22 原型模式 Prototype Pattern
下一篇
Day 24 抽象工廠模式 Abstract Factory Pattern
系列文
輕鬆學習設計模式Design Pattern30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言