軟體設計模式白話文系列(十一)享元模式

1、描述

以共享的方法高效地支援大量細粒度對象的復用。在 Java 中,通過提前初始化對象或者首次使用後記錄對象,後續使用就可以復用對象來實現享元模式。類似快取技術。

2、模式結構

  • 享元對象:可復用對象。

  • 享元工廠類:享元對象的工廠類,負責創建、儲存享元對象。客戶端從工廠類請求對象有則返回,沒有則創建

    一個放入工廠類。例如 String 類的快取池和資料庫的連接池。

3、實現邏輯

享元模式實現的關鍵是需要區分對象的內蘊狀態和外蘊狀態。簡單點解釋就是,內蘊狀態就是可被共享的部分;外蘊狀態就是不可共享的部分,需要客戶端提供的部分。Java 中實現享元模式,就是把內蘊部分剝離出來靜態化,客戶端調用時提供外蘊狀態(當然對象可以沒有外蘊部分)。

4、實戰程式碼

RBAC 模型基於角色的許可權控制。通過角色關聯用戶,角色關聯許可權的方式間接賦予用戶許可權。

我們知道對於用戶來講,每個用戶都有自己的 編號、姓名,但是會存在多個用戶都是同一個角色。在這裡編號、姓名就屬於外蘊狀態,而角色就屬於內蘊狀態。

/**
 * 享元對象
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-15 19:01:20
 */
@Data
public class Role {
    private String name;

    private List<String> permissions;
}

/**
 * 業務對象
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-15 19:00:57
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Member {

    private Long id;
    private String name;

    private Role role;
}

/**
 * 享元工廠類
 * 這裡結合靜態內部類單例模式實現 RoleFactory
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-15 19:04:47
 */
public class RoleFactory {
    private static Map<String, Role> roleMap = new HashMap<>();

    public RoleFactory() {
        Role admin = new Role();
        admin.setName("admin");
        admin.setPermissions(List.of("add", "update", "select", "delete"));

        Role user = new Role();
        user.setName("user");
        user.setPermissions(List.of("select"));

        roleMap.put("admin", admin);
        roleMap.put("user", user);
    }

    public Role getRole(String name) {
        return roleMap.get(name);
    }

    public static final RoleFactory getInstance() {
        return SingletonHolder.INSTANCE;
    }

    private static class SingletonHolder {
        private static final RoleFactory INSTANCE = new RoleFactory();
    }
}

/**
 * 測試類
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-15 19:13:48
 */
public class Client {
    public static void main(String[] args) {
        // 創建 10 個 Admin 用戶
        for (int i = 0; i < 10; i++) {
            Member member = new Member((long) i,
                    "admin" + i,
                    RoleFactory.getInstance().getRole("admin"));
            System.out.println(member);
        }
        System.out.println("------------------");
        // 創建 100 個 User 用戶
        for (int i = 0; i < 100; i++) {
            Member member = new Member((long) i,
                    "user" + i,
                    RoleFactory.getInstance().getRole("user"));
            System.out.println(member);
        }
    }
}

這樣我們通過提前創建 role 對象,使得頻繁創建 member 對象時復用 role 對象,減少了 role 對象的頻繁創建與銷毀,大大節約了記憶體佔用。

5、適用場景

相同對象或者相似對象需要頻繁創建時,適合使用享元模式。

6、享元模式與單例模式的區別

單例模式的目的是為了確保一個類只存在一個對象,需要自行實例化並提供全局訪問方法。

享元模式的目的是為了對對象內蘊部分的復用,無需保證一個類只存在一個對象。