1. 程式人生 > 其它 >12 結構型模式之享元模式(Flyweight)

12 結構型模式之享元模式(Flyweight)

技術標籤:設計模式

文章目錄


1 介紹

在面向物件程式設計過程中,有時會面臨要建立大量相同或相似物件例項的問題。建立那麼多的物件將會耗費很多的系統資源,它是系統性能提高的一個瓶頸。

1.1 定義和特點

享元(Flyweight)模式的定義:運用共享技術來有效地支援大量細粒度物件的複用。它通過共享已經存在的物件來大幅度減少需要建立的物件數量、避免大量相似類的開銷,從而提高系統資源的利用率。

享元(Flyweight)的核心思想很簡單:如果一個物件例項一經建立就不可變,那麼反覆建立相同的例項就沒有必要,直接向呼叫方返回一個共享的例項就行,這樣即節省記憶體,又可以減少建立物件的過程,提高執行速度。

享元模式的主要優點是:

  • 相同物件只要儲存一份,這降低了系統中物件的數量,從而降低了系統中細粒度物件給記憶體帶來的壓力

其主要缺點是:

  • 為了使物件可以共享,需要將一些不能共享的狀態外部化,這將增加程式的複雜性。
  • 讀取享元模式的外部狀態會使得執行時間稍微變長。

1.2 享元模式的結構

享元模式的定義提出了兩個要求,細粒度和共享物件。因為要求細粒度,所以不可避免地會使物件數量多且性質相近,此時我們就將這些物件的資訊分為兩個部分:內部狀態和外部狀態。

  • 內部狀態,即不會隨著環境的改變而改變的可共享部分。
  • 外部狀態,指隨環境改變而改變的不可以共享的部分。享元模式的實現要領就是區分應用中的這兩種狀態,並將外部狀態外部化。

主要角色:

  • 抽象享元角色(Flyweight):是所有的具體享元類的基類,為具體享元規範需要實現的公共介面,非享元的外部狀態以引數的形式通過方法傳入。
  • 具體享元(Concrete Flyweight)角色:實現抽象享元角色中所規定的介面。
  • 非享元(Unsharable Flyweight)角色:是不可以共享的外部狀態,它以引數的形式注入具體享元的相關方法中。
  • 享元工廠(Flyweight Factory)角色:負責建立和管理享元角色。當客戶物件請求一個享元物件時,享元工廠檢査系統中是否存在符合要求的享元物件,如果存在則提供給客戶;如果不存在的話,則建立一個新的享元物件。

2 案例

在實際應用中,享元模式主要應用於快取,即客戶端如果重複請求某些物件,不必每次查詢資料庫或者讀取檔案,而是直接返回記憶體中快取的資料。享元模式的設計思想是儘量複用已建立的物件,常用於工廠方法內部的優化。

比如俄羅斯方塊中:
在這裡插入圖片描述
形狀就上面幾種(I,L,T,Z)形狀中,但是顏色可以很多種

比如:如果這樣實現:
定義盒子模型:顏色和形狀

package study.wyy.design.flyweight.before;

import lombok.Data;

/**
 * @author wyaoyao
 * @description 俄羅斯方塊
 * @date 2021/1/7 13:32
 */
@Data
public class Box {

    /****
     * 顏色
     */
    private String color;

    /****
     * 形狀
     */
    private String shape;

    public Box(String color, String shape) {
        System.out.println("建立物件");
        this.color = color;
        this.shape = shape;
    }
}

定義一個工廠類

package study.wyy.design.flyweight.before;

/**
 * @author wyaoyao
 * @description
 * @date 2021/1/7 13:44
 */
public class BoxFactory {

    private BoxFactory() {
    }
    private static class SingletonHolder {
        private static final BoxFactory INSTANCE = new BoxFactory();
    }
    public static final BoxFactory getInstance() {
        return SingletonHolder.INSTANCE;
    }
    public Box create(String shape,String color){
        return new Box(color,shape);
    }
}

測試

package study.wyy.design.flyweight.before;

/**
 * @author wyaoyao
 * @description
 * @date 2021/1/7 13:46
 */
public class Client {
    public static void main(String[] args) {
        // 建立了了大量物件
        BoxFactory.getInstance().create("L","red");
        BoxFactory.getInstance().create("L","green");
        BoxFactory.getInstance().create("L","blue");
        BoxFactory.getInstance().create("L","orange");

    }
}

問題:

  • 每一次從工廠那物件都會建立物件。如果垃圾回收的不及時,還有可能出現記憶體洩露的問題。
  • 這裡只是簡單模擬,如果一個這個物件很複雜,每次都建立新的,其實很耗時的,並且每次建立的物件或許就很少的差異,共性更多,是不是可以減少物件的建立

簡單改造(享元模式):這樣只要形狀相同,便會取出已經建立好的物件,只改一下顏色(外部特性(少許的差異))

package study.wyy.design.flyweight.after;

import study.wyy.design.flyweight.before.Box;

import java.util.HashMap;

/**
 * @author wyaoyao
 * @description
 * @date 2021/1/7 13:52
 */
public class BoxFactory {
    private static HashMap<String, Box> map = new HashMap<>();
    private BoxFactory() {
    }
    private static class SingletonHolder {
        private static final BoxFactory INSTANCE = new BoxFactory();
    }
    public static final BoxFactory getInstance() {
        return SingletonHolder.INSTANCE;
    }
    public Box create(String shape, String color){
        Box box = map.get(shape);
        if(box==null) {
            // 建立新的
            box = new Box(color, shape);
            // 放到池子裡
            map.put(shape, box);
        }
        box.setColor(color);
        return box;

    }
}

3 Integer類

Integer為例,如果我們通過Integer.valueOf()這個靜態工廠方法建立Integer例項,當傳入的int範圍在-128~+127之間時,會直接返回快取的Integer例項:

 public static void main(String[] args) {
        Integer a = Integer.valueOf(2);
        Integer b = Integer.valueOf(2);
        System.out.println(a==b); // ture
        Integer c = Integer.valueOf(333);
        Integer d = Integer.valueOf(333);
        System.out.println(c==d); // false
    }

所以第一個是true,第二個是false

再補充一下:

Integer a = 5;

這種方式底層也是呼叫的valueof方法,用idea的反編譯可以看到:

ICONST_5
    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;