C++ 設計模式 - 享元模式 Flyweight Pattern

當你玩大型多人線上遊戲(MMO)時,畫面上有成百上千個玩家角色,擁有著不同的裝備、武器和坐騎,你有沒有想過,遊戲引擎是如何管理這麼多個體的?如果每一個角色都佔據完整的記憶體空間,電腦恐怕早已爆掉!這背後的祕密之一,就是我們今天要講的「享元模式」。

什麼是享元模式?

享元模式(Flyweight Pattern)是一種結構型設計模式,目的在減少物件建立時佔用的記憶體空間。它透過共享大量相似物件中的共通部分來降低記憶體使用,特別適合用在場景中有大量細微差異的物件。這樣的做法不僅節省了資源,也讓系統執行更有效率。

在享元模式中,會將可共享的部分抽出,放到單一實體中進行管理,而非每個物件都擁有自己的複本。對於那些不可共享的部分,則會透過引數進行傳遞,這樣就能避免重複建立類似的物件。

享元(Flyweight)這個術語源自拳擊界,在拳擊比賽中,Flyweight 指的是一個較輕量級(57kg以下)的選手級別。這個詞在設計模式中被借用,是因為享元模式的核心理念與輕量的概念相吻合:目的是減輕系統的記憶體負擔,讓程式變得更「輕」,更高效。享元模式透過共享物件的內部狀態,達到了減少記憶體佔用的效果。

享元模式在圖形系統中的應用

最常見的享元模式例子之一,就是在圖形系統中處理複雜的場景。想像你正在開發一個繪圖應用程式,必須在畫布上顯示上千個相同的圓形。每個圓的顏色、大小和座標可能不同,但圓的輪廓和基本形狀是相同的。我們可以利用享元模式,來共享這些圓的形狀,只根據需要改變其他屬性來展示不同的圓形。

首先我們需要定義一個共享的「圓形」物件,

1
2
3
4
5
6
7
8
9
10
11
12
// 圓形基類,提供繪製方法
class Circle {
public:
Circle(std::string color) : color(color) {}

void draw(int x, int y, int radius) {
std::cout << "Drawing " << color << " circle at (" << x << ", " << y << ") with radius " << radius << std::endl;
}

private:
std::string color;
};

然後我們可以建立一個「圓形工廠」,負責管理和提供共享的圓形物件,

1
2
3
4
5
6
7
8
9
10
11
12
13
class CircleFactory {
public:
std::shared_ptr<Circle> getCircle(const std::string& color) {
if (circles.find(color) == circles.end()) {
circles[color] = std::make_shared<Circle>(color);
std::cout << "Creating circle of color: " << color << std::endl;
}
return circles[color];
}

private:
std::unordered_map<std::string, std::shared_ptr<Circle>> circles;
};

接下來客戶端可以透過 CircleFactory 來取得不同顏色的圓形,並指定其座標和半徑,這樣便可以有效地共享相同顏色的圓形實體,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main() {
CircleFactory factory;

auto redCircle = factory.getCircle("Red");
redCircle->draw(10, 10, 5);

auto blueCircle = factory.getCircle("Blue");
blueCircle->draw(20, 20, 10);

auto anotherRedCircle = factory.getCircle("Red");
anotherRedCircle->draw(30, 30, 15);

return 0;
}

執行上述程式碼,我們會得到以下輸出:

1
2
3
4
5
Creating circle of color: Red
Drawing Red circle at (10, 10) with radius 5
Creating circle of color: Blue
Drawing Blue circle at (20, 20) with radius 10
Drawing Red circle at (30, 30) with radius 15

這裡我們重複使用了紅色圓形,而不必每次都建立新的紅色圓形。程式輸出將顯示紅色圓形只建立了一次,這就是享元模式的精髓——重複利用可共享的部分,節省記憶體。

享元模式的優缺點

享元模式最大的優點,當然就是節省記憶體。當我們需要建立大量相似的物件時,這種模式可以極大減少不必要的記憶體消耗,提升程式的效能。例如在繪圖系統、文字處理器,甚至是遊戲引擎中,都可以看到享元模式的影子。只要那些物件之間有許多相同的部分,就能採用享元模式進行共享。

然而這種共享資源的方式也不是沒有代價。享元模式的複雜性較高,因為需要將物件分為共享的內部狀態與不可共享的外部狀態。開發人員在使用時必須額外考慮如何管理這些狀態,避免產生混淆。另外在某些情況下,為了維護享元物件的內部狀態一致性,可能會導致更多的同步問題,特別是在多執行緒的環境下。

總結

享元模式是一種在需要大量建立相似物件的場景下非常有用的設計模式,透過共享物件來大幅減少記憶體消耗。在像是繪圖系統、遊戲開發等領域,我們可以利用這個模式來有效管理大量物件。然而,享元模式的實現需要仔細設計和管理內部狀態與外部狀態的區別,以避免增加不必要的複雜度。這個模式不會讓你的程式「飛起來」,但絕對能讓它執行得更順暢。