1.定義
使用共享對象可有效地支援大量的細粒度的對象。
是對象池技術的重要實作方式。
2.享元模式的使用場景
- 系統中存在大量的相似對象。
- 細粒度的對象都具備較接近的外部狀态,而且内部狀态與環境無關,也就是說對象沒有特定身份。
- 需要緩沖池的場景。
請看例子程式,學生和學校的關系:
package _22FlyweightPattern;
/**
* 班級類
*/
public class School {
// 學校id
private int id;
// 學校名稱
private String schoolName;
// 某學生對該學校的映像
private String desc;
// 還有很多資訊,比較占記憶體…………
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
package _22FlyweightPattern;
/**
* 學生類
*/
public class Student {
// 學生id
private int id;
// 學生姓名
private String name;
// 學生家庭住址
private String homeAddress;
// 所在學校
private School school;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getHomeAddress() {
return homeAddress;
}
public void setHomeAddress(String homeAddress) {
this.homeAddress = homeAddress;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
}
由于多個學生可以共用一個School對象,是以我們使用享元模式來分析School類。
我們将一個可共用的細粒度對象(上例中的School對象)的資訊分為兩類
- 内部狀态:類似于上面School類中的id、schoolName等等,不會随着環境的變化而變化。
- 外部狀态:類似于上面School類中的desc屬性,但是要注意,内部狀态和外部狀态不應該互相影響。外部狀态與對象本身無必然關系,外部狀态總是因為外界環境的改變而變化,也就是說外部狀态是由外界環境來決定的,無論你對學校的映像如何,學校都是同一個。
所謂的享元模式就是将一個類中可共享的部分抽取出來放進對象池,以避免重複建立相同(業務意義上的相同)的對象。
也就是下圖所表示的,要将第一張圖重構成第二張圖:
3.享元模式的四個角色
享元角色就是可共享的角色。
- Flyweight-抽象享元角色:它簡單的說就是一個産品的抽象類,同僚定義了對象的外部狀态和内部狀态的接口或實作。
- ConcreteFlyweight-具體享元角色:具體的一個産品類,實作抽象角色定義的業務。該角色需要注意的是内部狀态處理應該與環境無關,不應該出現一個操作改變了内部狀态,同僚又改變了外部狀态,這是絕對不允許的。(使用享元模式盡量不要修改内部狀态)
- unsharedConcreteFlyweight不可共享的享元角色:一般由N個具體享元角色組合而成,是以它本身不需要被緩存,不必出現在享元工廠中。
- FlyweightFactory享元工廠:職責非常簡單,就是構造一個池容器,同僚提供從池中獲得對象的方法。
下面是享元模式的類圖:
4.享元模式的通用代碼
package _22FlyweightPattern;
/**
* 抽象享元角色
*/
public abstract class Flyweight {
// 内部狀态
private String intrinsic;
// 外部狀态
protected final String extrinsic;
// 強制享元角色必須接受外部狀态
public Flyweight(String extrinsic)
{
this.extrinsic = extrinsic;
}
public String getIntrinsic() {
return intrinsic;
}
public void setIntrinsic(String intrinsic) {
this.intrinsic = intrinsic;
}
// 定義業務邏輯
public abstract void operate();
}
package _22FlyweightPattern;
/**
* 具體享元角色
*/
public class ConcreteFlyweight extends Flyweight {
public ConcreteFlyweight(String extrinsic) {
super(extrinsic);
}
@Override
public void operate() {
// 業務邏輯
}
}
package _22FlyweightPattern;
import java.util.HashMap;
import java.util.Map;
/**
* 享元工廠
*/
public class FlyweightFactory {
// 定義一個池容器
private static Map<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 ConcreteFlyweight(extrinsic);
pool.put(extrinsic, flyweight);
}
return flyweight;
}
}
5.享元模式的優缺點
享元模式是一個非常簡單的模式,它可以大大減少應用程式建立的對象(比如你使用new School()的方式10000個學生對象就會産生10000個School對象,而通過緩存技術,那麼隻存在一個School對象),降低程式記憶體的占用,增強程式的性能,但它同時也提高了系統複雜性,需要分理處内部和外部狀态,而且外部狀态具有固化特效,不應該随内部狀态改變而改變,否則導緻系統的邏輯混亂。
6.最佳實踐
外部狀态最好使用Java的基本類型作為标志,如String、int等,可以大幅提升效率(Map的key值)。
享元模式在Java API中随處可見,比如String會對字元串進行緩存,Integer會對-128到127的數字進行緩存。String的intern方法:如果String對象池中有該類型的值,那麼直接傳回對象池中的對象。