天天看點

22.享元模式(Flyweight Pattern)

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屬性,但是要注意,内部狀态和外部狀态不應該互相影響。外部狀态與對象本身無必然關系,外部狀态總是因為外界環境的改變而變化,也就是說外部狀态是由外界環境來決定的,無論你對學校的映像如何,學校都是同一個。

所謂的享元模式就是将一個類中可共享的部分抽取出來放進對象池,以避免重複建立相同(業務意義上的相同)的對象。

也就是下圖所表示的,要将第一張圖重構成第二張圖:

22.享元模式(Flyweight Pattern)
22.享元模式(Flyweight Pattern)

3.享元模式的四個角色

享元角色就是可共享的角色。

  • Flyweight-抽象享元角色:它簡單的說就是一個産品的抽象類,同僚定義了對象的外部狀态和内部狀态的接口或實作。
  • ConcreteFlyweight-具體享元角色:具體的一個産品類,實作抽象角色定義的業務。該角色需要注意的是内部狀态處理應該與環境無關,不應該出現一個操作改變了内部狀态,同僚又改變了外部狀态,這是絕對不允許的。(使用享元模式盡量不要修改内部狀态)
  • unsharedConcreteFlyweight不可共享的享元角色:一般由N個具體享元角色組合而成,是以它本身不需要被緩存,不必出現在享元工廠中。
  • FlyweightFactory享元工廠:職責非常簡單,就是構造一個池容器,同僚提供從池中獲得對象的方法。

下面是享元模式的類圖:

22.享元模式(Flyweight Pattern)

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對象池中有該類型的值,那麼直接傳回對象池中的對象。

繼續閱讀