初學 Java 設計模式(十二):實戰享元模式 「高校選課系統優化」

1、享元模式介紹

1. 解決的問題

主要解決載有大量對象時,可能形成內存溢出的問題。java

2. 定義

享元模式是一種結構型設計模式,它摒棄了在每一個對象中保存全部數據的方式,經過共享多個對象所共有的相同狀態,能在有限的內存容量中載入更多對象。git

3. 應用場景

  • 在程序必須支持大量對象且沒有足夠的內存容量時使用享元模式。

2、享元模式優缺點

1. 優勢

  • 程序中有不少類似對象,那就能節省大量內存。

2. 缺點

  • 可能須要犧牲執行速度來換取內存,由於每次調用向享元方法時都須要從新計算部分情景數據。
  • 代碼會變得更加複雜。如:爲何要拆分一個實體的狀態?

3、享元模式應用實例:高校選課系統優化

1. 實例場景

你們在學校選課時,應該都遇到太高峯期選課系統沒法使用,只能眼巴巴地等技術人員修復。github

爲何選課系統說崩就崩呢?有沒有一些簡單的方法就能對這類場景作優化呢?設計模式

還真有!選課系統主要就是要獲取咱們的選課信息,在咱們填寫選課信息後,提交到服務器,由服務起處理選課信息。緩存

通常來說,選課系統應該都是爲每一個學生選擇的每一個課程建立一個對象,而後提交到服務器。服務器

這種設計其實在訪問量不大的狀況下沒有什麼問題,但高併發狀況下,假設每一個人的課程信息對象平均大小爲 1M ,500 個選課請求僅課程信息對象的內存就達到 500M ,浪費了大量資源,這種大量內存消耗也容易致使 Out Of Memory。併發

接下來,就以選課系統做爲場景,介紹一下如何用享元模式優化。ide

2. 享元模式實現

2.1 工程結構
flyweight-pattern
└─ src
    ├─ main
    │    └─ java
    │    └─ org.design.pattern.flyweight
    │       ├─ model
    │       │    ├─ Course.java
    │       │    └─ Student.java
    │       ├─ factory
    │       │    └─ CourseFactory.java
    │       └─ service
    │            ├─ CourseSelectionService.java
    │            └─ impl
    │                └─ CourseSelectionServiceImpl.java
    └─ test
        └─ java
            └─ org.design.pattern.flyweight.test
                  └─ CourseSelectionTest.java
2.2 代碼實現
2.2.1 實體

課程高併發

/**
 * 課程
 */
@AllArgsConstructor
@Getter
public class Course {

    /**
     * 課程id
     */
    private String id;

    /**
     * 課程名
     */
    private String name;

    /**
     * 學分
     */
    private String credit;
}

學生測試

/**
 * 學生信息
 */
@Getter
@AllArgsConstructor
public class Student {

    /**
     * 學號
     */
    private String id;

    /**
     * 姓名
     */
    private String name;
}
2.2.2 工廠

課程工廠

/**
 * 課程工廠
 */
public class CourseFactory {

    private static final Logger log = LoggerFactory.getLogger(CourseFactory.class);

    /**
     * 課程對象池
     */
    private static Map<String, Course> courseMap = new ConcurrentHashMap<String, Course>();

    /**
     * 獲取課程
     *
     * @param dataMap 課程數據
     * @return Course
     */
    public static Course getCourse(Map<String, String> dataMap) {
        String id = dataMap.get("id");
        if (ObjectUtils.isEmpty(id)) {
            log.warn("course id is empty");
            return null;
        }
        if (ObjectUtils.isNotEmpty(courseMap.get(id))) {
            log.info("get cache course {}", id);
            return courseMap.get(id);
        } else {
            log.info("create new course {}", id);
            return createCourse(id, dataMap.get("name"), dataMap.get("credit"));
        }
    }

    /**
     * 建立課程
     *
     * @param id 課程id
     * @param name 課程名稱
     * @param credit 學分
     */
    public static Course createCourse(String id, String name, String credit) {
        if (ObjectUtils.isNotEmpty(id) && ObjectUtils.isNotEmpty(name) && ObjectUtils.isNotEmpty(credit)) {
            Course course = new Course(id, name, credit);
            courseMap.put(id, course);
            return course;
        }
        return null;
    }
}
2.2.3 服務

選課服務接口

/**
 * 選課服務接口
 */
public interface CourseSelectionService {

    /**
     * 選課
     *
     * @param student 學生
     * @param courseDataMap 課程數據
     */
    void selectCourse(Student student, Map<String, String> courseDataMap);
}

選課服務實現類

/**
 * 選課服務實現
 */
public class CourseSelectionServiceImpl implements CourseSelectionService {

    private static final Logger log = LoggerFactory.getLogger(CourseFactory.class);

    /**
     * 選課
     *
     * @param student 學生
     * @param courseDataMap 課程數據
     */
    @Override
    public void selectCourse(Student student, Map<String, String> courseDataMap) {
        Course course = CourseFactory.getCourse(courseDataMap);
        if (ObjectUtils.isEmpty(course)) {
            log.error("course is not exist");
            return;
        }
        log.info(
                "student {} select {} course, the course credit is {}",
                student.getName(), student.getName(), course.getCredit()
        );
    }
}
2.3 測試驗證
2.3.1 測試驗證類
/**
 * 選課測試類
 */
public class CourseSelectionTest {

    private CourseSelectionService courseSelectionService = new CourseSelectionServiceImpl();

    @Test
    public void testSelectCourse() {
        Map<String, String> courseDataMap = new HashMap<String, String>() {
            {
                put("id", "1");
                put("name", "高數一");
                put("credit", "5");
            }
        };
        Student studentA = new Student("1", "張三");
        courseSelectionService.selectCourse(studentA, courseDataMap);
        Student studentB = new Student("2", "李四");
        courseSelectionService.selectCourse(studentB, courseDataMap);
    }
}
2.3.2 測試結果
17:22:37.377 [main] INFO  o.d.p.f.factory.CourseFactory - create new course 1
17:22:37.380 [main] INFO  o.d.p.f.factory.CourseFactory - student 張三 select 張三 course, the course credit is 5
17:22:37.380 [main] INFO  o.d.p.f.factory.CourseFactory - get cache course 1
17:22:37.380 [main] INFO  o.d.p.f.factory.CourseFactory - student 李四 select 李四 course, the course credit is 5

Process finished with exit code 0

4、享元模式結構

享元模式-模式結構圖

享元模式只是一種優化。在應用該模式以前,要肯定程序中存在大量相似對象同時佔用內存相關的內存消耗問題,而且確保該問題沒法使用其餘更好的方式來解決。

  1. 享元(Flyweight)類包含原始對象中部分能在多個對象中共享的狀態。

    同一享元對象可在許多不一樣情景中使用。享元中存儲的狀態被稱爲 「內在狀態」 。傳遞給享元方法的狀態被稱爲 「外在狀態」 。

  2. 情景 (Context)類包含原始對象中各不相同的外在狀態。

    情景與享元對象組合在一塊兒就能表示原始對象的所有狀態。

  3. 一般狀況下,原始對象的行爲會保留在享元類中。所以享元方法必須提供部分外在狀態做爲參數。但也能夠將行爲移動到情景類中,而後將連入的享元做爲單純的數據對象。
  4. 客戶端(Client)負責計算或者存儲享元的外在狀態。在客戶端看來,享元是一種可在運行時進行配置的模板對象,具體的配置方式爲向其方法中傳入一些情景數據參數。
  5. 享元工廠(Flyweight Factory)會對已有享元的緩存池進行管理。

    有了工廠後,客戶端就無需直接建立享元,只需調用工廠並向其傳遞目標享元的一些內在狀態便可。工廠會根據參數在以前已建立的享元中進行查找,找到知足條件的享元將其返回,不然根據參數新建享元。

設計模式並不難學,其自己就是多年經驗提煉出的開發指導思想,關鍵在於多加練習,帶着使用設計模式的思想去優化代碼,就能構建出更合理的代碼。

源碼地址:https://github.com/yiyufxst/design-pattern-java

參考資料:
小博哥重學設計模式:https://github.com/fuzhengwei/itstack-demo-design
深刻設計模式:https://refactoringguru.cn/design-patterns/catalog

相關文章
相關標籤/搜索