設計模式---享元模式

2022-09-26 06:00:38

簡述

  • 型別:結構型
  • 目的:降低物件建立時大量屬性也隨之被新建而帶來的效能上的消耗

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

優化案例

最初版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. 當物件的絕大多數屬性與物件本身不是一對一而是一對多的關係時。換言之,多個物件公用一套屬性時