設計模式---享元模式

語言: CN / TW / HK

簡述

  • 類型:結構型
  • 目的:降低對象創建時大量屬性也隨之被新建而帶來的性能上的消耗

話不多説,我們看一個案例。

優化案例

最初版v0

現在需要採購一批辦公用的電腦,以下是 Computer 類的定義。

class Computer {
   	private String sn; // 序列號,電腦的唯一識別碼
    private String brand; // 品牌
    private String title; // 一個系列的名稱,如Lenovo的Thinkpad
    private String cpu;
    private String memory;
    private String disk;
    private String gpu;
    private String keyboard;
    private String display;
    public Computer(String sn, String brand, 
                    String title, String cpu, 
                    String memory, String disk, 
                    String gpu, String keyboard, 
                    String display) {
        this.sn = sn;
        this.brand = brand;
        this.title = title;
        this.cpu = cpu;
        this.memory = memory;
        this.disk = disk;
        this.gpu = gpu;
        this.keyboard = keyboard;
        this.display = display;
    }
}

現在公司要採購兩種電腦總計1000台,以下是模擬採購的代碼。

class Client {
    public static void main(String[] args) {
        List<Computer> purchase = new ArrayList<>();
        for (int i = 0; i < n; i ++) {
            purchase.add(new Computer(UUID.randomUUID().toString(),
                         "華為", "MateBook16", "鋭龍7 5800H標壓",
                         "16GB DDR4 雙通道", "512GB NVMe PCle SSD", 
                         "gpu", "全尺寸背光鍵盤", "16英寸");
        }
    }
}

循環中每一次都要生成一個新的Computer對象,並且該對象中有很多String類型的屬性,因為String是一個引用數據類型,所以會隨之生成很多的引用,從而降低系統的性能。實際上,採購的計算機只要型號相同,配置參數也就隨之相同且不會再改變,唯一會改變的其實就只有機器的序列號而已,所以我們沒有每追加一台電腦就重新設置一遍所有參數的必要。而且如果中途需要對於採購訂單的機器參數進行修改,那就必須迭代清單中的所有對象,對每個對象進行修改,又是一件效率低下的事。

為了解決這個問題,我們引入了享元模式。下面是修改後的代碼。

修改版v1

class Computer {
   	private String sn; // 序列號,電腦的唯一識別碼
    private ComputerSpec spec; // 依賴規格的具體屬性 → 依賴ComputerSpec類,迪米特法則
    public Computer(String sn, ComputerSpec spec) {
        this.sn = sn;
        this.spec = spec;
        this.title = title;
        this.model = model;
        this.cpu = cpu;
        this.memory = memory;
        this.disk = disk;
        this.gpu = gpu;
        this.keyboard = keyboard;
        this.display = display;
    }
}
enum ComputerSpec { // 定義一個計算機規格類
    MATEBOOK16("華為", "MateBook16", "鋭龍7 5800H標壓",
               "16GB DDR4 雙通道", "512GB NVMe PCle SSD", 
               "gpu", "全尺寸背光鍵盤", "16英寸");
    public String brand; // 品牌
    public String title; // 一個系列的名稱,如Lenovo的Thinkpad
    public String cpu;
    public String memory;
    public String disk;
    public String gpu;
    public String keyboard;
    public String display;
    ComputerSpec(String sn, String brand, 
                 String title, String cpu, 
                 String memory, String disk, 
                 String gpu, String keyboard, 
                 String display) {
        this.brand = brand;
        this.title = title;
        this.model = model;
        this.cpu = cpu;
        this.memory = memory;
        this.disk = disk;
        this.gpu = gpu;
        this.keyboard = keyboard;
        this.display = display;
    }
}

來看看修改後的採購如何模擬實現。

class Client {
    public static void main(String[] args) {
        List<Computer> purchase = new ArrayList<>();
        for (int i = 0; i < n; i ++) {
            purchase.add(new Computer(UUID.randomUUID().toString(), 
                                      ComputerSpec.MATEBOOK16));
        }
        // 由於訂單錯誤,現在需要批量將MateBook16修改為MateBook16s
        ComputerSpec.MATEBOOK16.title = "MateBook16s";
    }
}

使用享元模式,將 Computer 對象創建時不變的屬性封裝到 ComputerSpec 中, 內部狀態外部狀態 分開,內部狀態直接引用相同的數據源,而不是每次都重新生成新的數據,從而大幅提升系統性能。並且,需要對於數據統一修改時,由於數據源引用相同,只需要修改內部狀態的對應屬性即可修改所有數據。

ComputerSpec
sn

總結

優點

  1. 由於多個對象的屬性引用相同,從而極大程度的降低了系統性能的消耗。
  2. 由於多個屬性被封裝成新的類,對象與屬性間的依賴減少,從而降低了對象創建的複雜度。

缺點

  1. 增加了開發人員對於系統業務理解的難度。

適用場景

  1. 當對象的絕大多數屬性與對象本身不是一對一而是一對多的關係時。 換言之,多個對象公用一套屬性時