1、什麼是享元模式?
Use sharing to support large numbers of fine-grained objects efficiently.
享元模式(Flyweight Pattern):使用共享物件可有效地支援大量的細粒度的物件。
說人話:複用物件,節省記憶體。
2、享元模式定義
①、Flyweight——抽象享元角色
是一個產品的抽象類, 同時定義出物件的外部狀態和內部狀態的介面或實現。
一個物件資訊可以分為內部狀態和外部狀態。
內部狀態:物件可共享出來的資訊, 儲存在享元物件內部並且不會隨環境改變而改變,可以作為一個物件的動態附加資訊, 不必直接儲存在具體某個物件中, 屬於可以共享的部分。
外部狀態:物件得以依賴的一個標記, 是隨環境改變而改變的、 不可以共享的狀態。
②、ConcreteFlyweight——具體享元角色
具體的一個產品類, 實現抽象角色定義的業務。 該角色中需要注意的是內部狀態處理應該與環境無關, 不應該出現一個操作改變了內部狀態, 同時修改了外部狀態, 這是絕對不允許的。
③、unsharedConcreteFlyweight——不可共享的享元角色
不存在外部狀態或者安全要求(如執行緒安全) 不能夠使用共享技術的物件, 該物件一般不會出現在享元工廠中。
④、FlyweightFactory——享元工廠
職責非常簡單, 就是構造一個池容器, 同時提供從池中獲得物件的方法。
3、享元模式通用程式碼
/**
* 抽象享元角色
*/
public abstract class Flyweight {
// 內部狀態
private String instrinsic;
// 外部狀態 通過 final 修改,防止修改
protected final String extrinsic;
protected Flyweight(String extrinsic) {
this.extrinsic = extrinsic;
}
// 定義業務操作
public abstract void operate();
public String getInstrinsic() {
return instrinsic;
}
public void setInstrinsic(String instrinsic) {
this.instrinsic = instrinsic;
}
}
/**
* 具體享元角色1
*/
public class ConcreteFlyweight1 extends Flyweight{
protected ConcreteFlyweight1(String extrinsic) {
super(extrinsic);
}
@Override
public void operate() {
System.out.println("具體享元角色1");
}
}
/**
* 具體享元角色2
*/
public class ConcreteFlyweight2 extends Flyweight{
protected ConcreteFlyweight2(String extrinsic) {
super(extrinsic);
}
@Override
public void operate() {
System.out.println("具體享元角色2");
}
}
public class FlyweightFactory {
// 定義一個池容器
private static HashMap<String,Flyweight> pool = new HashMap<>();
// 享元工廠
public static Flyweight getFlyweight(String extrinsic){
// 需要返回的物件
Flyweight flyweight = null;
// 池中沒有該物件
if(pool.containsKey(extrinsic)){
flyweight = pool.get(extrinsic);
}else{
// 根據外部狀態建立享元物件
flyweight = new ConcreteFlyweight1(extrinsic);
// 放置到池中
pool.put(extrinsic,flyweight);
}
return flyweight;
}
}
4、通過享元設計文字編輯器
假設文字編輯器只包含文字編輯功能,而且只記錄文字和格式兩部分資訊,其中格式包括文字的字型型號、大小、顏色等資訊。
4.1 普通實現
通常設計是把每個文字看成一個單獨物件。
package com.itcoke.designpattern.flyweight.edittext;
/**
* 單個文字物件
*/
public class Character {
// 字元
private char c;
// 字型型號
private String font;
// 字型大小
private int size;
// 字型顏色
private int colorRGB;
public Character(char c, String font, int size, int colorRGB){
this.c = c;
this.font = font;
this.size = size;
this.colorRGB = colorRGB;
}
@Override
public String toString() {
return String.valueOf(c);
}
}
/**
* 編輯器實現
*/
public class Editor {
private ArrayList<Character> chars = new ArrayList<>();
public void appendCharacter(char c, String font, int size, int colorRGB){
Character character = new Character(c,font,size,colorRGB);
chars.add(character);
}
public void display(){
System.out.println(chars);
}
}
客戶端:
public class EditorClient {
public static void main(String[] args) {
Editor editor = new Editor();
editor.appendCharacter('A',"宋體",11,0XFFB6C1);
editor.appendCharacter('B',"宋體",11,0XFFB6C1);
editor.appendCharacter('C',"宋體",11,0XFFB6C1);
editor.display();
}
}
4.2 享元模式改寫
上面的問題很容易發現,每一個字元就會建立一個 Character 物件,如果是幾百萬個字元,那記憶體中就會存在幾百萬的物件,那怎麼去節省這些記憶體呢?
其實,分析一下,對於字型的格式,通常不會有很多,於是我們可以把字型格式設定為享元,也就是上面說的可以共享的內部狀態。
內部狀態(共享):字型型別、大小、顏色
外部狀態(不共享):字元
於是程式碼改寫如下:
public class CharacterStyle {
// 字型型號
private String font;
// 字型大小
private int size;
// 字型顏色
private int colorRGB;
public CharacterStyle(String font, int size, int colorRGB) {
this.font = font;
this.size = size;
this.colorRGB = colorRGB;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CharacterStyle that = (CharacterStyle) o;
return size == that.size &&
colorRGB == that.colorRGB &&
Objects.equals(font, that.font);
}
@Override
public int hashCode() {
return Objects.hash(font, size, colorRGB);
}
}
public class CharacterStyleFactory {
private static final Map<CharacterStyle,CharacterStyle> mapStyles = new HashMap<>();
public static CharacterStyle getStyle(String font, int size, int colorRGB){
CharacterStyle newStyle = new CharacterStyle(font,size,colorRGB);
if(mapStyles.containsKey(newStyle)){
return mapStyles.get(newStyle);
}
mapStyles.put(newStyle,newStyle);
return newStyle;
}
}
public class Character {
private char c;
private CharacterStyle style;
public Character(char c, CharacterStyle style) {
this.c = c;
this.style = style;
}
@Override
public String toString() {
return String.valueOf(c);
}
}
public class Editor {
private List<Character> chars = new ArrayList<>();
public void appendCharacter(char c, String font, int size, int colorRGB){
Character character = new Character(c,CharacterStyleFactory.getStyle(font,size,colorRGB));
chars.add(character);
}
public void display(){
System.out.println(chars);
}
}
5、享元模式在 java.lang.Integer 中應用
看下面這段程式碼,列印結果是啥?
public class IntegerTest {
public static void main(String[] args) {
Integer i1 = 56;
Integer i2 = 56;
Integer i3 = 129;
Integer i4 = 129;
System.out.println(i1 == i2);
System.out.println(i3 == i4);
}
}
為什麼是這種結果呢?
首先說一下 Integer i = 59;底層執行了:Integer i = Integer.valueOf(59); 這是自動裝箱。
int j = i; 底層執行了:int j = i.intValue(); 這是自動拆箱。
然後我們Integer.valueOf() 方法:
再看 IntegerCache 原始碼:
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
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) {
// If the property cannot be parsed into an int, ignore it.
}
}
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() {}
}
其實這就是我們前面說的享元物件的工廠類,快取 -128 到 127 之間的整型值,這是最常用的一部分整型值,當然JDK 也提供了方法來讓我們可以自定義快取的最大值。
6、享元模式優點
減少應用程式建立的物件, 降低程式記憶體的佔用, 增強程式的效能。
但它同時也提高了系統複雜性, 需要分離出外部狀態和內部狀態, 而且外部狀態具有固化特性, 不應該隨內部狀態改變而改變, 否則導致系統的邏輯混亂。
7、享元模式應用場景
①、系統中存在大量的相似物件。
②、細粒度的物件都具備較接近的外部狀態, 而且內部狀態與環境無關, 也就是說物件沒有特定身份。
③、需要緩衝池的場景。