軟體設計模式系列之十三——享元模式

cooldream2009發表於2023-09-23

1 模式的定義

享元模式(Flyweight Pattern)是一種結構型設計模式,它旨在減少記憶體佔用或計算開銷,透過共享大量細粒度物件來提高系統的效能。這種模式適用於存在大量相似物件例項,但它們的狀態可以外部化(extrinsic),並且可以在多個物件之間共享的情況。

2 舉例說明

為了更好地理解享元模式,讓我們舉一些現實生活中的例子。

咖啡店的咖啡杯和碟子的例子。在咖啡店中,咖啡杯和碟子通常具有相同的設計和形狀,但它們可能具有不同的顏色或圖案。咖啡店可以使用享元模式來共享相同設計的杯子和碟子,以減少儲存和管理的成本。

公共交通卡的例子。城市中的公共交通卡(如地鐵卡、公共汽車卡)通常具有相同的功能和外觀,但每張卡可能包含不同的餘額和個人資訊。這些卡可以被視為享元物件,公共交通系統可以共享卡的通用功能。

電子書閱讀器的字型和樣式的例子。電子書閱讀器可以使用享元模式來管理字型、字號和樣式。多本電子書可以共享相同的字型和樣式設定,以提供一致的閱讀體驗。

這些例子都涉及到具有相似屬性和功能的物件,它們可以透過享元模式來共享通用部分,從而減少資源消耗並提高效率。這在設計和生產中可以節省時間和成本。

3 結構

享元模式的結構包括以下主要元件:

享元工廠(Flyweight Factory):享元工廠負責建立和管理享元物件。它維護一個享元池,其中包含已經建立的享元物件,並根據客戶端請求共享已經存在的物件或建立新的享元物件。

享元介面(Flyweight Interface):享元介面是享元物件的抽象,通常宣告瞭享元物件的公共方法,以便客戶端能夠訪問和操作享元物件。

具體享元(Concrete Flyweight):具體享元是享元介面的實現,包含了內部狀態和外部狀態。內部狀態是可以被共享的,而外部狀態是不可共享的,它在執行時傳遞給享元物件。

客戶端(Client):客戶端是使用享元模式的應用程式或模組,它透過享元工廠來獲取或共享享元物件,並根據需要傳遞外部狀態。

4 實現步驟

要實現享元模式,可以按照以下步驟進行操作:

確定內部狀態和外部狀態:首先,確定物件的內部狀態和外部狀態。內部狀態是可以被多個物件共享的部分,而外部狀態是不可共享的。

建立享元介面:定義享元介面,宣告享元物件的公共方法,包括操作內部狀態和外部狀態的方法。

建立具體享元類:實現具體享元類,它包含了內部狀態和外部狀態的具體實現。內部狀態可以在多個物件之間共享,而外部狀態需要在執行時傳遞。

建立享元工廠:建立享元工廠,負責建立和管理享元物件。享元工廠可以維護一個享元池,用於儲存已經建立的享元物件。

客戶端使用享元物件:在客戶端中,透過享元工廠來獲取或共享享元物件。客戶端需要提供外部狀態作為引數,並根據需要操作享元物件。

5 程式碼實現

以下是一個簡單的 Java 程式碼示例,演示瞭如何使用享元模式來實現公共交通卡的共享功能。在這個示例中,我們建立了一個 TransportCardFactory 工廠類來管理交通卡物件,以及一個 TransportCard 介面表示交通卡。

// 1. 定義交通卡介面
interface TransportCard {
    void swipe();
}

// 2. 建立具體的交通卡類
class SubwayCard implements TransportCard {
    private String ownerName;
    private int balance;

    public SubwayCard(String ownerName) {
        this.ownerName = ownerName;
        this.balance = 0;
    }

    public void swipe() {
        System.out.println("刷地鐵卡,扣除票價,餘額:" + balance);
    }
}

// 3. 建立享元工廠類
class TransportCardFactory {
    private Map<String, TransportCard> cards = new HashMap<>();

    public TransportCard getCard(String ownerName) {
        if (cards.containsKey(ownerName)) {
            System.out.println("使用現有的交通卡:" + ownerName);
            return cards.get(ownerName);
        } else {
            System.out.println("建立新的交通卡:" + ownerName);
            TransportCard card = new SubwayCard(ownerName);
            cards.put(ownerName, card);
            return card;
        }
    }
}

// 4. 客戶端程式碼
public class Client {
    public static void main(String[] args) {
        TransportCardFactory cardFactory = new TransportCardFactory();

        // 乘客1刷卡
        TransportCard card1 = cardFactory.getCard("zhanngsan");
        card1.swipe();

        // 乘客2刷卡
        TransportCard card2 = cardFactory.getCard("lisi");
        card2.swipe();

        // 再次刷卡
        TransportCard card3 = cardFactory.getCard("zhanngsan");
        card3.swipe();
    }
}

在這個示例中,我們首先定義了 TransportCard 介面,表示交通卡的通用功能。然後,我們建立了一個具體的交通卡類 SubwayCard,它實現了 TransportCard 介面,幷包含了特定於地鐵卡的屬性。

接下來,我們建立了享元工廠類 TransportCardFactory,它負責管理和共享交通卡物件。當客戶端需要一個交通卡時,工廠類會首先檢查是否已經存在具有相同擁有者姓名的卡,如果存在則返回現有的卡,否則建立一個新的卡物件。

最後,我們在客戶端程式碼中演示瞭如何使用享元模式,建立並刷卡,觀察到當兩位乘客使用相同姓名刷卡時,會共享同一個交通卡物件,從而減少了卡物件的建立和記憶體佔用。

6 典型應用場景

6.1 享元模式通常在以下情況下得到廣泛應用

  • 大量物件。當系統中存在大量相似物件例項時,使用享元模式可以顯著減少記憶體佔用,因為相似物件的內部狀態可以共享。

  • 內部狀態與外部狀態。當物件可以分為內部狀態和外部狀態時,享元模式特別有用。內部狀態是物件的固定部分,可以被多個物件共享,而外部狀態是物件的可變部分,每個物件可以根據需要個性化。

  • 效能最佳化。在需要高效能和低記憶體消耗的情況下,享元模式可以用於共享重複使用的物件,從而提高系統的效能。

  • 快取管理。在需要快取大量物件以提高系統響應時間的情況下,可以使用享元模式來管理快取物件。

  • 資源池管理。當需要管理共享資源池(如資料庫連線池、執行緒池)中的資源物件時,享元模式可以用於有效地共享和重用資源。

享元模式在需要管理大量相似物件、共享內部狀態、提高效能和減少記憶體佔用的情況下非常有用。它允許物件在不同上下文中共享內部狀態,而外部狀態可以根據需要進行個性化定製。透過合理使用享元模式,可以改善系統的效率和資源利用率。

6.2 java中的字串應用享元模式場景

在Java中,字串是使用享元模式的經典示例。享元模式的核心思想是共享相似物件的內部狀態,以減少記憶體佔用。字串的使用正是基於這個思想。

下面是Java中字串如何使用享元模式的一些關鍵特點:

不可變性:Java中的字串是不可變的,也就是說一旦建立了一個字串物件,它的值就不能被修改。這意味著如果兩個字串具有相同的字元序列,它們可以共享相同的內部字元陣列。

字串常量池:Java維護了一個字串常量池(String Pool),用於儲存字串字面量。當你建立一個字串字面量時,Java會首先檢查常量池中是否已經存在相同值的字串。如果存在,它將返回常量池中的字串引用,而不會建立新的物件。

共享相同的字串物件:由於字串的不可變性和字串常量池的存在,多個字串變數可以共享相同的字串物件。這意味著如果你有多個字串變數引用相同的字串值,它們實際上共享同一個字串物件。

下面是一個示例,演示了字串如何使用享元模式:

String s1 = "Hello"; // 建立一個字串字面量,儲存在常量池中
String s2 = "Hello"; // 與s1共享相同的字串物件

String s3 = new String("Hello"); // 建立一個新的字串物件,不儲存在常量池中
String s4 = new String("Hello"); // 建立另一個新的字串物件,也不儲存在常量池中

System.out.println(s1 == s2); // true,s1和s2共享相同的字串物件
System.out.println(s1 == s3); // false,s1和s3引用不同的字串物件

在上面的示例中,s1 和 s2 共享相同的字串物件,因為它們引用相同的字串字面量,而 s3 和 s4 建立了新的字串物件,因為它們使用了 new 運算子。這種共享內部狀態的方式減少了記憶體佔用,並提高了效能,特別是當處理大量字串時。

Java中的字串是一個典型的享元模式的例子,透過不可變性和字串常量池,它實現了字串物件的共享,以減少記憶體佔用和提高效能。這種設計對於處理字串操作非常高效,並且保證了字串值的安全性,因為它們不可被修改。

7 優缺點

享元模式具有一些優點和缺點,讓我們來看看:

優點:

減少記憶體佔用,享元模式透過共享相似物件的內部狀態,可以大大減少記憶體佔用,提高系統的效能和效率。提高效能,透過共享物件,減少了物件的建立和銷燬,從而提高了系統的效能。分離內部狀態和外部狀態,享元模式允許將內部狀態和外部狀態分開,外部狀態可以在執行時傳遞給享元物件,使系統更靈活。

缺點:

增加複雜性,享元模式引入了共享物件和外部狀態的概念,可能增加了系統的複雜性。可能導致執行緒安全問題,如果多個執行緒同時訪問共享物件並修改其外部狀態,可能會導致執行緒安全問題。

8 類似模式

享元模式通常與其他設計模式一起使用,以解決更復雜的問題或實現更全面的系統。以下是一些常見的設計模式,以及如何與享元模式一起使用它們。

工廠模式。享元模式通常需要一個工廠來建立和管理共享物件。你可以使用工廠模式來建立享元物件,確保物件的建立和初始化過程是封裝的,並且客戶端不需要直接建立物件。

單例模式。在享元模式中,享元工廠可以是一個單例,以確保只有一個享元工廠例項用於管理共享物件。這樣可以確保物件的唯一性和一致性。

裝飾者模式:裝飾者模式可以與享元模式一起使用,以動態地新增功能或狀態到享元物件。裝飾者模式允許你在不改變物件結構的情況下,為物件新增額外的行為。

代理模式:代理模式可以與享元模式一起使用,以提供對享元物件的訪問控制或延遲載入。代理可以用於監控或限制對共享物件的訪問。

組合模式:組合模式用於將物件組織成樹形結構,享元模式可以用於共享組合中的相似物件,以減少記憶體佔用。這在圖形和影像處理應用中特別有用。

享元模式可以與許多其他設計模式一起使用,具體取決於系統的需求。它通常與建立型模式(如工廠模式、單例模式)和結構型模式(如裝飾者模式、代理模式)結合使用,以實現更靈活、高效和可維護的系統。在實際應用中,將多種模式結合使用可以更好地滿足複雜系統的需求。

9 小結

享元模式是一種有助於減少記憶體佔用和提高系統效能的結構型設計模式。透過共享大量細粒度的物件,它可以有效地降低系統的資源消耗,特別適用於存在大量相似物件的場景。在設計和開發中,當需要建立大量相似物件時,可以考慮使用享元模式以提高系統的效率和效能。這種模式的核心思想是將物件的內部狀態與外部狀態分離,從而實現物件的共享和複用。

相關文章