設計模式(結構型)之享元模式(Flyweight Pattern)
PS一句:最終還是選擇CSDN來整理髮表這幾年的知識點,該文章平行遷移到CSDN。因為CSDN也支援MarkDown語法了,牛逼啊!
概述
當一個軟體系統在執行時產生的物件數量太多,將導致執行代價過高,帶來系統性能下降等問題。所以需要採用一個共享來避免大量擁有相同內容物件的開銷。在Java中,String型別就是使用了享元模式。String物件是final型別,物件一旦建立就不可改變。在Java中字串常量都是存在常量池中的,Java會確保一個字串常量在常量池中只有一個拷貝。
核心
概念: 運用共享技術有效地支援大量細粒度物件的複用。系統只使用少量的物件,而這些物件都很相似,狀態變化很小,可以實現物件的多次複用。由於享元模式要求能夠共享的物件必須是細粒度物件,因此它又稱為輕量級模式,它是一種物件結構型模式。
關於享元基礎:
享元物件共享的關鍵是區分了內部狀態(Intrinsic State)和外部狀態(Extrinsic State)。
內部狀態
儲存在享元物件內部並且不會隨環境改變而改變的狀態,內部狀態可以共享。
外部狀態
享元物件的外部狀態通常由客戶端儲存,並在享元物件被建立之後,需要使用的時候再傳入到享元物件內部。隨環境改變而改變的、不可以共享的狀態。一個外部狀態與另一個外部狀態之間是相互獨立的。
由於區分了內部狀態和外部狀態,我們可以將具有相同內部狀態的物件儲存在享元池中,享元池中的物件是可以實現共享的,需要的時候就將物件從享元池中取出,實現物件的複用。通過向取出的物件注入不同的外部狀態,可以得到一系列相似的物件,而這些物件在記憶體中實際上只儲存一份。
享元模式分類:
- 單純享元模式
- 複合享元模式
單純享元模式結構重要核心模組:
抽象享元角色
為具體享元角色規定了必須實現的方法,而外部狀態就是以引數的形式通過此方法傳入。在Java中可以由抽象類、介面來擔當。
具體享元角色
實現抽象角色規定的方法。如果存在內部狀態,就負責為內部狀態提供儲存空間。
享元工廠角色
負責建立和管理享元角色。要想達到共享的目的,這個角色的實現是關鍵!
客戶端角色
維護對所有享元物件的引用,而且還需要儲存對應的外部狀態。
單純享元模式和建立型的簡單工廠模式實現上非常相似,但是它的重點或者用意卻和工廠模式截然不同。工廠模式的使用主要是為了使系統不依賴於實現得細節;而在享元模式的主要目的是避免大量擁有相同內容物件的開銷。
複合享元模式結構重要核心模組:
抽象享元角色
為具體享元角色規定了必須實現的方法,而外部狀態就是以引數的形式通過此方法傳入。在Java中可以由抽象類、介面來擔當。
具體享元角色
實現抽象角色規定的方法。如果存在內部狀態,就負責為內部狀態提供儲存空間。
複合享元角色
它所代表的物件是不可以共享的,並且可以分解成為多個單純享元物件的組合。
享元工廠角色
負責建立和管理享元角色。要想達到共享的目的,這個角色的實現是關鍵!
客戶端角色
維護對所有享元物件的引用,而且還需要儲存對應的外部狀態。
使用場景
一個系統有大量相同或者相似的物件,造成記憶體的大量耗費。
物件的大部分狀態都可以外部化,可以將這些外部狀態傳入物件中。
在使用享元模式時需要維護一個儲存享元物件的享元池,而這需要耗費一定的系統資源,因此,應當在需要多次重複使用享元物件時才值得使用享元模式。
程式猿例項
單純享元模式例項:例子完全就是核心點的文字翻譯程式碼,不做過多解釋。
package yanbober.github.io;
import java.util.HashMap;
import java.util.Map;
//抽象享元角色類
interface ICustomerString {
//外部狀態以引數的形式通過此方法傳入
void opt(String state);
}
//具體享元角色類
class CustomerStringImpl implements ICustomerString {
//負責為內部狀態提供儲存空間
private Character mInnerState = null;
public CustomerStringImpl(Character mInnerState) {
this.mInnerState = mInnerState;
}
@Override
public void opt(String state) {
System.out.println("Inner state = "+this.mInnerState);
System.out.println("Out state = "+state);
}
}
//享元工廠角色類
//一般而言,享元工廠物件在整個系統中只有一個,因此也可以使用單例模式
class CustomerStringFactory {
private Map<Character, ICustomerString> map = new HashMap<>();
public ICustomerString factory(Character state) {
ICustomerString cacheTemp = map.get(state);
if (cacheTemp == null) {
cacheTemp = new CustomerStringImpl(state);
map.put(state, cacheTemp);
}
return cacheTemp;
}
}
//客戶端
public class Main {
public static void main(String[] args) {
CustomerStringFactory factory = new CustomerStringFactory();
ICustomerString customerString = factory.factory(new Character('Y'));
customerString.opt("YanBo");
customerString = factory.factory(new Character('B'));
customerString.opt("Bob");
customerString = factory.factory(new Character('Y'));
customerString.opt("Jesse");
}
}
執行結果:
Inner state = Y
Out state = YanBo
Inner state = B
Out state = Bob
Inner state = Y
Out state = Jesse
上邊示例結果一目瞭然可以看出來簡單享元模式的特點。
複合享元模式例項:
如下例子就是一個複合享元模式,添加了複合物件,具體如下:
package yanbober.github.io;
import java.util.*;
//抽象享元角色類
interface ICustomerString {
//外部狀態以引數的形式通過此方法傳入
void opt(String state);
}
//具體享元角色類
class CustomerStringImpl implements ICustomerString {
//負責為內部狀態提供儲存空間
private Character mInnerState = null;
public CustomerStringImpl(Character mInnerState) {
this.mInnerState = mInnerState;
}
@Override
public void opt(String state) {
System.out.println("Inner state = "+this.mInnerState);
System.out.println("Out state = "+state);
}
}
//複合享元物件
class MultipleCustomerStringImpl implements ICustomerString {
private Map<Character, ICustomerString> map = new HashMap<>();
public void add(Character key, ICustomerString value) {
map.put(key, value);
}
@Override
public void opt(String state) {
ICustomerString temp;
for (Character obj : map.keySet()) {
temp = map.get(obj);
temp.opt(state);
}
}
}
//享元工廠角色類
class CustomerStringFactory {
//一般而言,享元工廠物件在整個系統中只有一個,因此也可以使用單例模式
private Map<Character, ICustomerString> map = new HashMap<>();
//上例的單純享元模式
public ICustomerString factory(Character state) {
ICustomerString cacheTemp = map.get(state);
if (cacheTemp == null) {
cacheTemp = new CustomerStringImpl(state);
map.put(state, cacheTemp);
}
return cacheTemp;
}
//複合享元模式
public ICustomerString factory(List<Character> states) {
MultipleCustomerStringImpl impl = new MultipleCustomerStringImpl();
for (Character state : states) {
impl.add(state, this.factory(state));
}
return impl;
}
}
//客戶端
public class Main {
public static void main(String[] args) {
List<Character> states = new ArrayList<>();
states.add('Y');
states.add('A');
states.add('N');
states.add('B');
states.add('O');
states.add('Y');
states.add('B');
CustomerStringFactory factory = new CustomerStringFactory();
ICustomerString customerString1 = factory.factory(states);
ICustomerString customerString2 = factory.factory(states);
customerString1.opt("Mutex object test!");
}
}
總結一把
從上面程式碼你可以發現,由於享元模式的複雜,實際應用也不是很多,這是我們更加無法看清他的真面目了。不過享元模式並不是雞肋,它的精髓是共享,是對我們系統優化非常有好處的,而且這種思想已經別越來越多的應用,這應該就算是享元模式的應用了吧。
享元模式優點:
- 可以極大減少記憶體中物件的數量,使得相同或相似物件在記憶體中只儲存一份,從而可以節約系統資源,提高系統性能。
- 享元模式的外部狀態相對獨立,而且不會影響其內部狀態,從而使得享元物件可以在不同的環境中被共享。
享元模式缺點:
- 享元模式使得系統變得複雜,需要分離出內部狀態和外部狀態,這使得程式的邏輯複雜化。
- 為了使物件可以共享,享元模式需要將享元物件的部分狀態外部化,而讀取外部狀態將使得執行時間變長。