定義:
運用共用技術來有效地支援大量細粒度物件的複用。它通過共用已經存在的物件來大幅度減少需要建立的物件數量、避免大量相似物件的開銷,從而提高系統資源的利用率。
享元(Flyweight )模式中存在以下兩種狀態:
內部狀態,即不會隨著環境的改變而改變的可共用部分。
外部狀態,指隨環境改變而改變的不可以共用的部分。享元模式的實現要領就是區分應用中的這兩種狀態,並將外部狀態外部化。
享元模式的主要有以下角色:
抽象享元角色(Flyweight):通常是一個介面或抽象類,在抽象享元類中宣告了具體享元類公共的方法,這些方法可以向外界提供享元物件的內部資料(內部狀態),同時也可以通過這些方法來設定外部資料(外部狀態)。
具體享元(Concrete Flyweight)角色 :它實現了抽象享元類,稱為享元物件;在具體享元類中為內部狀態提供了儲存空間。通常我們可以結合單例模式來設計具體享元類,為每一個具體享元類提供唯一的享元物件。
非享元(Unsharable Flyweight)角色 :並不是所有的抽象享元類的子類都需要被共用,不能被共用的子類可設計為非共用具體享元類;當需要一個非共用具體享元類的物件時可以直接通過範例化建立。
享元工廠(Flyweight Factory)角色 :負責建立和管理享元角色。當客戶物件請求一個享元物件時,享元工廠檢査系統中是否存在符合要求的享元物件,如果存在則提供給客戶;如果不存在的話,則建立一個新的享元物件。
【例】俄羅斯方塊
下面的圖片是眾所周知的俄羅斯方塊中的一個個方塊,如果在俄羅斯方塊這個遊戲中,每個不同的方塊都是一個範例物件,這些物件就要佔用很多的記憶體空間,下面利用享元模式進行實現。
先來看類圖:
程式碼如下:
俄羅斯方塊有不同的形狀,我們可以對這些形狀向上抽取出AbstractBox,用來定義共性的屬性和行為。
// 抽象享元角色
public abstract class AbstractBox {
public abstract String getShape();
public void display(String color) {
System.out.println("方塊形狀:" + this.getShape() + " 顏色:" + color);
}
}
接下來就是定義不同的形狀了,IBox類、LBox類、OBox類等。
//(具體享元角色)
public class IBox extends AbstractBox {
@Override
public String getShape() {
return "I";
}
}
public class LBox extends AbstractBox {
@Override
public String getShape() {
return "L";
}
}
public class OBox extends AbstractBox {
@Override
public String getShape() {
return "O";
}
}
提供了一個工廠類(BoxFactory),用來管理享元物件(也就是AbstractBox子類物件),該工廠類物件只需要一個,所以可以使用單例模式。並給工廠類提供一個獲取形狀的方法。
//工廠類,將該類設計為單例
public class BoxFactory {
private static HashMap<String, AbstractBox> map;
//在構造方法中進行初始化操作
private BoxFactory() {
map = new HashMap<String, AbstractBox>();
AbstractBox iBox = new IBox();
AbstractBox lBox = new LBox();
AbstractBox oBox = new OBox();
map.put("I", iBox);
map.put("L", lBox);
map.put("O", oBox);
}
//提供一個方法獲取該工廠類物件
public static final BoxFactory getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final BoxFactory INSTANCE = new BoxFactory();
}
public AbstractBox getBox(String key) {
return map.get(key);
}
}
測試類
public class Client {
public static void main(String[] args) {
//獲取I圖形物件
AbstractBox box1 = BoxFactory.getInstance().getShape("I");
box1.display("灰色");
//獲取L圖形物件
AbstractBox box2 = BoxFactory.getInstance().getShape("L");
box2.display("綠色");
//獲取O圖形物件
AbstractBox box3 = BoxFactory.getInstance().getShape("O");
box3.display("灰色");
//獲取O圖形物件
AbstractBox box4 = BoxFactory.getInstance().getShape("O");
box4.display("紅色");
System.out.println("兩次獲取到的O圖形物件是否是同一個物件:" + (box3 == box4));
}
}
測試結果
1,優點
極大減少記憶體中相似或相同物件數量,節約系統資源,提供系統效能
享元模式中的外部狀態相對獨立,且不影響內部狀態,上面案例中的顏色,相同形狀不同顏色是同一個物件。
2,缺點:
為了使物件可以共用,需要將享元物件的部分狀態外部化,分離內部狀態和外部狀態,使程式邏輯複雜
3,使用場景:
一個系統有大量相同或者相似的物件,造成記憶體的大量耗費,比如執行緒池或連線池。
物件的大部分狀態都可以外部化,可以將這些外部狀態傳入物件中。
在使用享元模式時需要維護一個儲存享元物件的享元池(上面案例中的hashmap),而這需要耗費一定的系統資源,因此,應當在需要多次重複使用享元物件時才值得使用享元模式。
Integer類使用了享元模式。我們先看下面的例子:
public class Demo {
public static void main(String[] args) {
// 自動裝箱
Integer i1 = 127;
Integer i2 = 127;
System.out.println("i1和i2物件是否是同一個物件?" + (i1 == i2));
Integer i3 = 128;
Integer i4 = 128;
System.out.println("i3和i4物件是否是同一個物件?" + (i3 == i4));
}
}
執行上面程式碼,結果如下:
為什麼第一個輸出語句輸出的是true,第二個輸出語句輸出的是false?通過反編譯軟體進行反編譯,程式碼如下:
public class Demo {
public static void main(String[] args) {
Integer i1 = Integer.valueOf((int)127);
Integer i2 Integer.valueOf((int)127);
System.out.println((String)new StringBuilder().append((String)"i1\u548ci2\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i1 == i2)).toString());
Integer i3 = Integer.valueOf((int)128);
Integer i4 = Integer.valueOf((int)128);
System.out.println((String)new StringBuilder().append((String)"i3\u548ci4\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i3 == i4)).toString());
}
}
上面程式碼可以看到,直接給Integer型別的變數賦值基本資料型別資料的操作底層使用的是 valueOf()
,所以只需要看該方法即可
public final class Integer extends Number implements Comparable<Integer> {
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
}
可以看到 Integer
預設先建立並快取 -128 ~ 127
之間數的 Integer
物件,當呼叫 valueOf
時如果引數在 -128 ~ 127
之間則計算下標並從快取中返回,否則建立一個新的 Integer
物件。
本文來自部落格園,作者:|舊市拾荒|,轉載請註明原文連結:https://www.cnblogs.com/xiaoyh/p/16560058.html