設計模式之享元模式

fomalhaut_sh發表於2021-04-22

享元模式

模式介紹

享元模式可以理解為一個共享池的概念,即將一個物件快取起來,下次再用的時候直接在快取中獲取,這樣就不用重新建立物件,達到了節省記憶體、優化程式效率的優點。比如我們常用的String 和 資料庫的連線池都是運用了該模式的思想。

應用場景

當程式中需要大量的細粒度物件,這些物件內容相似,並且可以按照兩種狀態區分的時候,就可以使用該模式進行設計。

優缺點

  • 優點

    1. 由於建立的物件都被快取了起來,所以在此請求相同的物件的時候,就不用在重新建立物件,直接從快取中獲取該物件即可。
    2. 節省了記憶體開銷和程式的效率。
  • 缺點

    1. 增加系統的理解難度。
    2. 需要將物件分為兩種狀態並且根據這兩種狀態(內部、外部)來控制是否進行物件的建立。
    3. 需要維護一個共享池,可以理解為工廠模式的實現。

例子介紹

比如我們現在有一個需求,需要在一塊畫布上隨機位置,展示一個圓,這個圓分為四種顏色,分別為 “紅、藍、黃、綠” , 如果使用普通的做法來做就是,客戶端發起請求,服務端接收後,根據請求的顏色去建立相應的圓形物件,然後賦予座標。

package cn.hsh.study.flyweight.ordinary;

/**
 * @author shaohua
 * @date 2021/4/22 19:41
 */
public class Circular {

    private String color;

    private int x;

    private int y;

    public Circular(String color, int x, int y) {
        this.color = color;
        this.x = x;
        this.y = y;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    @Override
    public String toString() {
        return "Circular{" +
                "color='" + color + '\'' +
                ", x=" + x +
                ", y=" + y +
                '}';
    }
}

package cn.hsh.study.flyweight.ordinary;

/**
 * @author shaohua
 * @date 2021/4/22 19:41
 */
public class Client {

    public static void main(String[] args) {

        Circular red = new Circular("red", 1, 10);
        System.out.println(red);
    }
}

結果如下:

綜上我們可以看到,客戶端每次請求的時候都會建立一個新的物件,如果請求了1000次紅色圓形那麼就會建立1000個圓形物件出來,這個可以發現,除了座標不同,1000個物件顏色是一樣的,這裡我們就可以將 顏色 和 座標,分為內部狀態和外部狀態,內部狀態為顏色可以進行共享但是不可以修改,外部狀態就是座標,不可以共享,但是可以隨著客戶端的呼叫而修改。 看程式碼 ↓

package cn.hsh.study.flyweight;

/**
 * @author shaohua
 * @date 2021/4/20 19:40
 */
public class Circular {

    private String color;

    private int x;
    private int y;

    public String getColor() {
        return color;
    }


    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public Circular(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Circular{" +
                "color='" + color + '\'' +
                ", x=" + x +
                ", y=" + y +
                '}';
    }
}

首先定義實體物件類,這個可以繼承圖形抽象類,但是我這裡為了節省就直接寫死了一個類。

package cn.hsh.study.flyweight.factory;

import cn.hsh.study.flyweight.Circular;

import java.util.HashMap;
import java.util.Map;

/**
 * @author shaohua
 * @date 2021/4/20 19:41
 */
public class FlyWeightFactory {

    private static Map<String, Circular> pools = new HashMap<String, Circular>(16);

    public static Circular factory(String color, int x, int y){
        Circular circular;
        if(pools.containsKey(color)){
            System.out.println("檢測到共享池中存在該顏色直接返回");
            circular = pools.get(color);
        } else {
            circular = new Circular(color);
        }
        circular.setX(x);
        circular.setY(y);
        pools.put(color, circular);
        return circular;
    }
}

注意 這裡是整個享元模式的核心,也就是我們上面說的工廠類。每次請求的時候使用內部狀態(顏色)為key,然後外部狀態根據客戶端可以隨意修改。最後返回。

package cn.hsh.study.flyweight;

import cn.hsh.study.flyweight.factory.FlyWeightFactory;

import java.util.LinkedList;
import java.util.List;

/**
 * @author shaohua
 * @date 2021/4/19 18:45
 */
public class Client {


    public static void main(String[] args) {
        Circular red = FlyWeightFactory.factory("red", 1, 10);
        Circular blue = FlyWeightFactory.factory("blue", 2, 20);
        Circular yellow = FlyWeightFactory.factory("yellow", 3, 30);
        Circular green = FlyWeightFactory.factory("green", 4, 40);
        Circular red1 = FlyWeightFactory.factory("red", 5, 50);
        Circular blue1 = FlyWeightFactory.factory("blue", 6, 60);

        List<Circular> circulars = new LinkedList<Circular>();
        circulars.add(red);
        circulars.add(blue);
        circulars.add(yellow);
        circulars.add(green);
        circulars.add(red1);
        circulars.add(blue1);

        for (Circular circular : circulars) {
            System.out.println(circular.toString());
        }

        System.out.println(red == red1);
        System.out.println(blue == blue1);
    }

}

這是客戶端呼叫,直接看結果 ↓

可以看到,每個顏色的圓形在第一次呼叫的時候都會快取到共享池中,第二次呼叫的時候返回的物件是共享池中被建立好了的物件,只是修改了座標(x, y) 屬性而已,物件還是同一個。所以就做到了不用每次請求都去建立一個物件,即節省了記憶體的開支,也優化了程式的效率。

個人觀點

該模式在單執行緒下可以正常使用,一旦用在併發高的需求上可能會在客戶端賦予外部狀態的時候出現併發問題,所以該模式需要謹慎使用。

總結

  • 在以下情況下可以使用享元模式

    1. 一個系統有大量相同或者相似的物件,由於這類物件的大量使用,造成記憶體的大量耗費;
      物件的大部分狀態都可以外部化,可以將這些外部狀態傳入物件中(細粒度物件);
      使用享元模式需要維護一個儲存享元物件的享元池,而這需要耗費資源,因此,應當在多次重複使用享元物件時才值得使用享元模式。
  • 模式的優點

    1. 它可以極大減少記憶體中物件的數量,使得相同物件或相似物件在記憶體中只儲存一份;
    2. 享元模式的外部狀態相對獨立,而且不會影響其內部狀態,從而使得享元物件可以在不同的環境中被共享。
  • 模式的缺點

    1. 享元模式使得系統更加複雜,需要分離出內部狀態和外部狀態,這使得程式的邏輯複雜化;
    2. 為了使物件可以共享,享元模式需要將享元物件的狀態外部化,而讀取外部狀態使得執行時間變長。

相關文章