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;