設計模式系列之十二:享元模式
阿新 • • 發佈:2019-02-10
1.定義
使用共享物件可有效的支援大量的細粒度的物件。
享元模式是池技術的重要實現方式,享元模式的定義為我們提出了兩個要求,細粒度物件和共享物件。我們知道分配太多的物件到以程式中將有損程式的效能,還會造成記憶體溢位,享元模式正是為此而生的。
說到細粒度物件,不可避免地使得物件數量多且性質相近,可以將物件資訊分為兩個部分:內部狀態(intrinic)與外部狀態(extrinisic)
- 內部狀態
物件可以共享出來的資訊,儲存在物件內部並不會隨著環境的改變而改變,它們可以作為一個物件的動態附加資訊,不必直接儲存在具體某個物件中,屬於可以共享的部分。 - 外部狀態
是物件得以依賴的一個標記,是隨環境改變而改變的、不可以共享的狀態。
2.類圖
角色介紹
- Flyweight 抽象享元角色:一個產品的抽象類,同時定義出物件的內部狀態和外部狀態的介面或實現。
- ConcreteFlyweight 具體享元角色:具體的一個產品類,實現抽象角色定義的業務。該角色中需要注意的是內部狀態處理應該與環境無關,不應該出現一個操作改變了內部狀態,同時改變了外部狀態,絕對不允許。
- unsharedConcreteFlyweight 不可共享的享元角色:不存在外部狀態或者安全要求(如執行緒安全)不能夠使用共享技術的物件,該物件一般不會出現在享元工廠中。
- FlyweightFactory 享元工廠:構造一個池容器,同時提供從池中獲得物件的方法
3.通用原始碼
抽象享元角色
public abstract class Flyweight {
//內部狀態
private String intrinsic;
//外部狀態
protected final String Extrinsic;
//要求享元角色必須接受外部狀態
public Flyweight (String _Extrinsic) {
this.Extrinsic = _Extrinsic;
}
//定義業務操作
public abstract void operate();
//內部狀態的getter/setter
public String getIntrinsic() {
return intrinsic;
}
public void setIntrinsic(String intrinsic) {
this.intrinsic = intrinsic;
}
}
具體享元模式
public class ConcreteFlyweight1 extends Flyweight {
//接受外部狀態
public ConcreteFlyweight1(String _Extrinsic) {
super(_Extrinsic);
}
//根據外部狀態進行邏輯處理
public void operate() {
//業務邏輯
}
}
public class ConcreteFlyweight2 extends Flyweight {
//接受外部狀態
public ConcreteFlyweight2(String _Extrinsic) {
super(_Extrinsic);
}
//根據外部狀態進行邏輯處理
public void operate() {
//業務邏輯
}
}
享元工廠
public class FlyweightFactory {
//定義一個池容器
private static HashMap<String,Flyweight> pool = new HashMap<String,Flyweight>();
//享元工廠
public static Flyweight getFlyweight(String Extrinsic) {
//要返回的物件
Flyweight flyweight = null;
//在池中沒有該物件
if(pool.containsKey(Extrinsic)) {
flyweight = pool.get(Extrinsic);
}else {
//根據外部狀態建立享元物件
flyweight = new ConcreteFlyweight1(Extrinsic);
}
return flyweight;
}
}
4.Demo
假設現在有一個報考系統,有一個模組負責社會人員報名,該模組只開放三天,並限制報考人員的數量,兩天系統宕了4次。原因是每個人報名都會建立一個物件,物件太多,導致記憶體耗盡,現在用享元模式,優化。
報考資訊類
public class SignInfo {
private String id; // 報名人員的id
private String location; // 考試地點
private String subject; // 考試科目
private String postAddress; // 郵寄地址
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getPostAddress() {
return postAddress;
}
public void setPostAddress(String postAddress) {
this.postAddress = postAddress;
}
}
帶物件池的報考資訊
public class SignInfo4Pool extends SignInfo {
//定義一個物件池提取的key值
private String key;
//建構函式獲得相同標誌
public SignInfo4Pool(String _key) {
this.key = _key;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
}
帶物件池的工廠類
public class SignInfoFactory {
//池容器
private static HashMap<String,SignInfo> pool = new HashMap<String,SignInfo>();
//報名資訊工廠
@Deprecated
public static SignInfo getSignInfo() {
return new SignInfo();
}
//從池中獲取物件
public static SignInfo getSignInfo(String key) {
SignInfo result = null; //設定返回物件
//池中沒有該物件,則建立,放入池中
if(!pool.containsKey(key)) {
System.out.println(key + "---建立物件,並放入到池中");
result = new SignInfo4Pool(key);
pool.put(key, result);
}else {
result = pool.get(key);
System.out.println(key + "---直接從池中取得");
}
return result;
}
}
場景類
public class Client {
public static void main(String[] args) {
//初始化物件池
for (int i = 0; i < 4; i++) {
String subject = "科目"+i;
//初始化地址
for (int j = 0; j <30; j++) {
String key = subject + "考試地點" + j;
SignInfoFactory.getSignInfo(key);
}
}
SignInfo signInfo = SignInfoFactory.getSignInfo("科目1考試地點1");
}
}
執行結果:
最後一個物件,直接從容器裡取的,這個物件是帶有科目和考試地點,之後每建一個物件都會享有科目和考試資訊。
5.應用場景
- 系統中存在大量相似的物件
- 細粒度的物件都具備較接近的外部狀態,而且內部狀態與環境無關,也就是說物件沒有特定身份
- 需要緩衝池
6.優缺點
- 優點
- 減少應用程式建立物件,降低程式記憶體的佔用
- 增強程式的效能
- 缺點
- 提高了系統複雜性
- 需要分離出外部狀態和內部狀態,而且外部狀態具有固定化性,不應隨內部狀態的改變而改變,否則會導致系統的邏輯混亂。
參考資料
01. 秦曉波.《設計模式之禪》. 機械工業出版社 :2010年