文章目錄
- C++中實現享元模式通常涉及以下幾個關鍵部分:
- 一個簡單的C++代碼片段示例
- 享元模式的進一步說明
- C++享元模式代碼案例——咖啡店訂單系統
- 享元模式在現實世界的應用場景
C++中實現享元模式通常涉及以下幾個關鍵部分:
享元模式(Flyweight Pattern)是一種用于性能優化的設計模式,它通過共享對象來有效支持大量的細粒度對象,以減少內存消耗。這種模式適用于那些對象中大部分狀態可以外部化,而只有少量內部狀態(不隨環境改變而改變的狀態)的對象。在C++中實現享元模式通常涉及以下幾個關鍵部分:
- 抽象享元(Flyweight Interface):
定義了所有具體享元類共有的接口,通常包含內部狀態和外部狀態的操作方法。內部狀態是可以共享的,而外部狀態由客戶端在每次調用時傳入。
class IFlyweight {
public:virtual ~IFlyweight() {}// 內部狀態不變的操作virtual void operation(const Context& context) const = 0;
};
- 具體享元(Concrete Flyweight):
實現了抽象享元接口,并存儲內部狀態。具體享元類的實例是可共享的。
class ConcreteFlyweight : public IFlyweight {
private:std::string intrinsicState; // 內部狀態
public:ConcreteFlyweight(const std::string& state) : intrinsicState(state) {}void operation(const Context& context) const override {// 使用內部狀態和外部狀態進行操作}
};
- 享元工廠(Flyweight Factory):
負責創建和管理享元對象。它確保當請求的是相同的內部狀態時,不會創建多個具有相同內部狀態的享元對象。
class FlyweightFactory {
private:std::map<std::string, std::shared_ptr<IFlyweight>> flyweights;
public:std::shared_ptr<IFlyweight> getFlyweight(const std::string& key) {if (flyweights.find(key) == flyweights.end()) {flyweights[key] = std::make_shared<ConcreteFlyweight>(key);}return flyweights[key];}
};
一個簡單的C++代碼片段示例
下面是一個簡單的C++代碼片段示例,展示了如何使用享元模式:
#include <iostream>
#include <string>
#include <map>
#include <memory>// 抽象享元
class IFlyweight {
public:virtual ~IFlyweight() {}virtual void operation(const std::string& extrinsicState) const = 0;
};// 具體享元
class ConcreteFlyweight : public IFlyweight {
private:std::string intrinsicState;
public:ConcreteFlyweight(const std::string& state) : intrinsicState(state) {}void operation(const std::string& extrinsicState) const override {std::cout << "Concrete Flyweight: Internal State = " << intrinsicState<< ", Extrinsic State = " << extrinsicState << std::endl;}
};// 享元工廠
class FlyweightFactory {
private:std::map<std::string, std::shared_ptr<IFlyweight>> flyweights;
public:std::shared_ptr<IFlyweight> getFlyweight(const std::string& key) {if (flyweights.count(key) == 0) {flyweights[key] = std::make_shared<ConcreteFlyweight>(key);}return flyweights[key];}
};int main() {FlyweightFactory factory;std::vector<std::string> extrinsicStates = {"state1", "state2", "state2"};for (const auto& state : extrinsicStates) {auto flyweight = factory.getFlyweight("sharedState");flyweight->operation(state);}return 0;
}
在這個例子中,每當客戶端請求一個享元對象時,工廠會檢查是否已經存在具有相同內部狀態的對象。如果存在,則返回已有的對象;如果不存在,則創建新的具體享元對象并存儲起來供后續請求使用。這樣,多個帶有不同外部狀態的對象就可以共享同一個具有固定內部狀態的具體享元對象,從而節約內存。
讓我們深入探討一下上面代碼示例的實際意義和應用場景。在上述例子中,ConcreteFlyweight
類的內部狀態是字符串 "sharedState"
,它是可共享的。而外部狀態則是傳遞給 operation()
方法的參數 extrinsicState
,每次調用時可能不同。
例如,假設我們正在創建一個文本渲染引擎,其中有許多字符需要繪制,但許多字符共享同一張圖片資源。在這種情況下,字符的形狀(字體樣式、顏色等)可以視為內部狀態,這部分是固定的且可以共享;而字符的位置、旋轉角度、縮放比例等則可以視為外部狀態,這些屬性會隨著字符在不同上下文中的使用而變化。
class CharacterFlyweight : public IFlyweight {
private:Texture texture; // 內部狀態,表示字符圖像紋理
public:CharacterFlyweight(const Texture& tex) : texture(tex) {}void operation(const RenderingContext& context) const override {// 使用共享的紋理資源,根據context中的位置、旋轉角度等繪制字符}
};class CharacterFactory {
private:std::map<CharacterStyle, std::shared_ptr<IFlyweight>> characters;
public:std::shared_ptr<IFlyweight> getCharacter(const CharacterStyle& style) {if (characters.find(style) == characters.end()) {Texture tex = loadTexture(style.getFontFile()); // 加載圖片資源characters[style] = std::make_shared<CharacterFlyweight>(tex);}return characters[style];}
};struct RenderingContext {Point position;double rotation;float scale;// 其他外部狀態...
};int main() {CharacterFactory factory;// 渲染不同位置的同一種字符CharacterStyle sharedStyle("Arial.ttf");RenderingContext ctx1{Point{10, 20}, 0.0, 1.0};RenderingContext ctx2{Point{30, 40}, 0.0, 1.0};auto character = factory.getCharacter(sharedStyle);character->operation(ctx1);character->operation(ctx2);return 0;
}
在這個例子中,CharacterFlyweight
類是具體的享元類,存儲了字符的紋理(內部狀態)。CharacterFactory
類作為享元工廠,負責創建和管理字符享元對象,保證相同的字符樣式只加載一次圖片資源。每次需要渲染字符時,客戶端獲取對應字符享元對象并傳入上下文(即外部狀態),從而有效地減少了重復加載資源造成的內存消耗。
享元模式的進一步說明
進一步說明,享元模式(Flyweight Pattern)的核心目標是通過共享技術有效支持大量細粒度的對象,從而節省系統資源。在上述文本渲染引擎的例子中:
-
CharacterFlyweight
是享元類,它封裝了字符的基本視覺表現——也就是共享的紋理資源(內部狀態),并且提供了operation()
方法來執行實際的渲染操作,該方法接受RenderingContext
對象,包含了外部狀態如位置、旋轉角度和縮放比例。 -
CharacterFactory
負責管理和創建享元對象,確保相同字符樣式只創建一個實例。當請求某個樣式的字符時,如果該樣式對應的享元對象尚不存在,則加載相應的紋理資源并創建新的CharacterFlyweight
實例;若已經存在,則直接返回已有的實例。 -
RenderingContext
表示每個字符實例的特定環境或配置,這是外部狀態的具體體現,不被多個字符實例共享。每渲染一個字符時,都會根據當前的渲染上下文調整字符的表現形式。
在實際應用中,通過這樣的設計,即便文檔中有成千上萬個同類型的字符需要渲染,也只需要保存一份共享的紋理資源,大大降低了系統的內存占用。同時,由于外部狀態的變化不影響內部狀態的復用,使得系統能靈活應對各種不同的渲染需求。
除此之外,享元模式還有助于減少系統中對象的數量,進而降低系統運行時的內存消耗和CPU開銷。特別是在大型系統中,合理運用享元模式能夠顯著提升性能和響應速度。不過需要注意的是,不是所有的系統或場景都適用享元模式,應根據具體情況判斷是否滿足以下條件:
-
對象數量龐大且內部狀態大部分可以共享:如果系統中存在大量相似對象,且這些對象之間的大部分狀態是相同的,那么就可以考慮使用享元模式。
-
內部狀態較少且相對穩定:內部狀態是指那些不會隨著環境改變而改變的狀態,外部狀態則是與具體使用環境相關的狀態。享元對象應該盡量少地持有內部狀態,并通過參數傳遞外部狀態。
-
對象的創建成本高:如果對象的創建過程比較耗時或耗費資源,通過享元模式復用已有對象可以顯著減少這些成本。
總結起來,享元模式在游戲開發、圖形渲染、數據庫連接池、緩存系統等領域有廣泛應用。在實現時需權衡好內存消耗和程序邏輯復雜度的關系,確保模式的有效性和易維護性。
C++享元模式代碼案例——咖啡店訂單系統
以下是一個簡單的C++享元模式代碼案例,該案例模擬了一個咖啡店訂單系統,其中咖啡口味被視為享元對象,可以被多個訂單共享:
#include <iostream>
#include <map>
#include <memory>// 抽象享元接口
class CoffeeFlavor {
public:virtual ~CoffeeFlavor() {}virtual void serve() const = 0;
};// 具體享元類
class ConcreteCoffeeFlavor : public CoffeeFlavor {
public:explicit ConcreteCoffeeFlavor(const std::string& flavorName): flavorName_(flavorName) {}void serve() const override {std::cout << "Serving coffee flavor: " << flavorName_ << std::endl;}private:std::string flavorName_;
};// 享元工廠
class CoffeeFlavorFactory {
private:std::map<std::string, std::shared_ptr<CoffeeFlavor>> flavors;public:std::shared_ptr<CoffeeFlavor> getOrder(const std::string& flavor) {if (flavors.find(flavor) == flavors.end()) {flavors[flavor] = std::make_shared<ConcreteCoffeeFlavor>(flavor);}return flavors[flavor];}
};int main() {CoffeeFlavorFactory factory;// 創建幾個不同的訂單,但某些口味會被共享std::vector<std::string> orders = {"Espresso", "Latte", "Espresso", "Cappuccino", "Espresso"};for (const auto& order : orders) {auto flavor = factory.getOrder(order);flavor->serve();}return 0;
}
在這個例子中:
CoffeeFlavor
是抽象享元類,定義了所有咖啡口味的基本行為,即服務(serve)咖啡。ConcreteCoffeeFlavor
是具體享元類,實現了咖啡口味的具體行為,并存儲了口味名稱這一內部狀態。CoffeeFlavorFactory
是享元工廠,它維護了一個儲存所有咖啡口味實例的映射表。當客戶請求某個口味的咖啡時,工廠會檢查是否已經創建過該口味的實例,如果沒有則創建一個新的實例,否則返回已有的實例,從而實現了口味的共享。
運行此代碼,可以看到雖然訂單列表中有多個"Espresso",但在打印結果中只會看到一次"Serving coffee flavor: Espresso",這是因為享元模式讓多次請求相同口味的咖啡共享了同一個實例。
實際上,上述咖啡口味享元模式的案例并沒有體現出享元模式對外部狀態的處理。在一個更全面的示例中,我們可能還會遇到咖啡訂單具有外部狀態,如客戶的偏好(加糖、加奶)、杯子大小等。這時,我們可以對示例稍作修改,將外部狀態從享元對象中分離出來:
#include <iostream>
#include <map>
#include <memory>// 抽象享元接口
class CoffeeFlavor {
public:virtual ~CoffeeFlavor() {}virtual void serve(const std::string& extras, const std::string& size) const = 0;
};// 具體享元類
class ConcreteCoffeeFlavor : public CoffeeFlavor {
public:explicit ConcreteCoffeeFlavor(const std::string& flavorName): flavorName_(flavorName) {}void serve(const std::string& extras, const std::string& size) const override {std::cout << "Serving " << size << " cup of " << flavorName_<< " with extras: " << extras << std::endl;}private:std::string flavorName_;
};// 享元工廠
class CoffeeFlavorFactory {
private:std::map<std::string, std::shared_ptr<CoffeeFlavor>> flavors;public:std::shared_ptr<CoffeeFlavor> getOrder(const std::string& flavor) {if (flavors.find(flavor) == flavors.end()) {flavors[flavor] = std::make_shared<ConcreteCoffeeFlavor>(flavor);}return flavors[flavor];}
};// 訂單類,包含外部狀態
class CoffeeOrder {
public:CoffeeOrder(std::shared_ptr<CoffeeFlavor> flavor, const std::string& extras, const std::string& size): flavor_(flavor), extras_(extras), size_(size) {}void serve() const {flavor_->serve(extras_, size_);}private:std::shared_ptr<CoffeeFlavor> flavor_;std::string extras_;std::string size_;
};int main() {CoffeeFlavorFactory factory;// 創建幾個不同的訂單,但某些口味會被共享std::vector<CoffeeOrder> orders = {{factory.getOrder("Espresso"), "no sugar", "small"},{factory.getOrder("Latte"), "extra foam", "medium"},{factory.getOrder("Espresso"), "double sugar", "large"},{factory.getOrder("Cappuccino"), "cinnamon", "medium"},{factory.getOrder("Espresso"), "single sugar", "small"}};for (const auto& order : orders) {order.serve();}return 0;
}
在這個修改后的示例中,我們創建了一個CoffeeOrder
類,它包含了外部狀態(額外配料和杯子大小),并在serve()
方法中將這些外部狀態傳遞給享元對象。即使多個訂單選擇了相同的咖啡口味,由于外部狀態的不同,每個訂單都能得到個性化的服務。
享元模式在現實世界的應用場景
討論享元模式在現實世界的應用場景,除了前面提到的咖啡訂單系統以外,還有很多其他例子可以借鑒:
-
字體渲染:在圖形用戶界面或者文字處理軟件中,同一字體的不同實例可以共享字體文件數據,字體的尺寸、顏色、陰影等效果可以作為外部狀態傳遞給字體享元對象。
-
圖形渲染:在游戲開發中,大量的小顆粒物(如草地上的草葉、森林里的樹葉等)可以共享同樣的紋理和模型數據,而位置、旋轉角度、縮放比例等作為外部狀態傳遞。
-
數據庫連接池:在Web服務器中,數據庫連接是非常寶貴的資源,通過享元模式可以復用已建立的數據庫連接,避免頻繁創建和銷毀連接帶來的性能損耗。這里的連接對象就是享元對象,連接參數(數據庫地址、用戶名、密碼等)則是外部狀態。
-
HTTP 請求緩存:在Web服務中,針對相同的URL發起的GET請求可以復用之前請求的結果,而不是每次都重新發送請求。這里HTTP請求結果可以看作享元對象,請求參數和URL作為外部狀態。
在上述各個場景中,享元模式通過共享內部狀態(那些不隨環境變化而變化的部分)的實例,有效地節省了系統資源,提升了整體性能。同時,它通過分離內部狀態和外部狀態,使得系統能夠靈活地應對各種復雜的業務場景。
python推薦學習匯總連接:
50個開發必備的Python經典腳本(1-10)
50個開發必備的Python經典腳本(11-20)
50個開發必備的Python經典腳本(21-30)
50個開發必備的Python經典腳本(31-40)
50個開發必備的Python經典腳本(41-50)
————————————————
?最后我們放松一下眼睛