天天看點

結構型-享元模式

享元模式(Flyweight Pattern):以共享的方式高效的支援大量的細粒度對象。通過複用記憶體中已存在的對象,降低系統建立對象執行個體的性能消耗。

享元即為共享元對象,在面向對象中,建立對象是很消耗資源的,享元模式就是為避免産生大量的細粒度對象提供解決方案的。

享元模式有兩種狀态,内蘊狀态和外蘊狀态,内蘊狀态存儲于享元對象内部,不會随環境而改變,可以共享;外蘊狀态随外部環境改變,并且由用戶端儲存,不能共享。

享元模式類圖:

結構型-享元模式
涉及到的角色如下:

抽象享元:為具體享元類規定出公共接口;

具體享元:實作抽象享元類接口,如果有内蘊狀态,需要為内蘊狀态提供存儲空間,這是保證共享的必要條件;

享元工廠:負責建立和管理享元角色,保證享元對象可以被共享,例如用戶端擷取享元對象時,需要判斷該類型對象是否存在,存在則直接傳回,不存在則建立然後傳回;

用戶端:需要維護對所需享元對象的引用,如果有外蘊狀态,需要将外蘊狀态儲存在用戶端;           

模式案例

五子棋中會用到很多的黑子和白子,如果對于每一個黑子或白子都建立一個對象會太消耗記憶體。可以使用享元模式,使得在整個遊戲中隻有“黑子”和“白子”兩個對象。

首先建立一個棋子抽象類作為棋子的超類,含有一個棋子辨別的屬性:

/**
 * 棋子的超類,含有一個棋子類别的屬性,标志具體的棋子類型
 */
public abstract class AbstractChessman {
    //棋子類别
    protected String chess;

    //構造方法
    public AbstractChessman(String chess){
        this.chess = chess;
    }

    //顯示棋子資訊
    public void show(){
        System.out.println(this.chess);
    }
}           

黑子類:

/**
 * 需求:黑子類
 */
public class BlackChessman extends AbstractChessman {
    /*
     * 構造方法,初始化黑棋子
     */
    public BlackChessman(){
        super("●");
        System.out.println("--一顆黑棋子誕生了!--");
    }
    
}           

白子類:

/**
 * 需求:白棋子
 *
 */
public class WhiteChessman extends AbstractChessman {
    /*
     * 構造方法,初始化黑棋子
     */
    public WhiteChessman(){
        super("○");
        System.out.println("--一顆白棋子誕生了!--");
    }
}           

下面來設計棋子工廠類,棋子工廠類我們設計為單例模式,該類用來生産棋子對象執行個體,并放入緩存當中,下次再獲得棋子對象的時候就從緩存當中獲得。内容如下:

/**
 * 需求:棋子工廠,用于生産棋子對象執行個體,并放入緩存中,采用單例模式完成
 */
public class ChessmanFactory {
    //單例模式
    private static ChessmanFactory chessmanFactory = new ChessmanFactory();
    //緩存共享對象
    private final Hashtable<Character, AbstractChessman> cache = new Hashtable<Character, AbstractChessman>();
    //構造方法私有化
    private ChessmanFactory(){
    }
    //獲得單例工廠對象
    public static ChessmanFactory getInstance(){
        return chessmanFactory;
    }
    /*
     * 根據字母獲得棋子
     */
    public AbstractChessman getChessmanObject(char c){        
        //從緩存中獲得棋子對象執行個體
        AbstractChessman abstractChessman = this.cache.get(c);
        //判空
        if (abstractChessman==null) {
            //說明緩存中沒有該棋子對象執行個體,需要建立
            switch (c) {
            case 'B':
                abstractChessman = new BlackChessman();
                break;
            case 'W':
                abstractChessman = new WhiteChessman();
                break;
            default:
                System.out.println("非法字元,請重新輸入!");
                break;
            }
            //如果有非法字元,那麼對象必定仍為空,是以再進行判斷
            if (abstractChessman!=null) {
                //放入緩存
                this.cache.put(c, abstractChessman);
            }
        }
        //如果緩存中存在棋子對象則直接傳回
        return abstractChessman;
    }
}           

通過用戶端進行測試:

public class Test {
    public static void main(String[] args) {
        //建立工廠
        ChessmanFactory chessmanFactory = ChessmanFactory.getInstance();
        //随機數,用于生成棋子對象
        Random random = new Random();
        int radom = 0;
        AbstractChessman abstractChessman = null;
        //随機獲得棋子
        for (int i = 0; i < 10; i++) {
            radom = random.nextInt(2);
            switch (radom) {
            case 0:
                //獲得黑棋子
                abstractChessman = chessmanFactory.getChessmanObject('B');
                break;
            case 1:
                //獲得黑棋子
                abstractChessman = chessmanFactory.getChessmanObject('W');
                break;
            }
            if (abstractChessman!=null) {
                abstractChessman.show();
            }
        }
    }
}           

執行後,我們發現“一顆黑棋子誕生了!”和“一顆白棋子誕生了!”各執行了一次,說明在衆多棋子中隻有一個黑棋子和一個白棋子,實作了對象的共享,這就是享元。

外蘊狀态

上邊例子使用到了内蘊狀态,内蘊狀态對于任何一個享元對象來講是完全相同的,可以說,内蘊狀态保證了享元對象能夠被共享。例如上邊的“黑子”和“白子”,代表的狀态就是内蘊狀态。

如果我們為棋子添加位置資訊,因為棋子位置都是不一樣的,是不能夠共享的,這需要使用外蘊狀态。享元對象的外蘊狀态與内蘊狀态是兩類互相獨立的狀态,彼此沒有關聯。

增加棋子位置資訊的抽象類為:

/**
 * 需求:棋子的超類,含有一個棋子類别的屬性,标志具體的棋子類型
 */
public abstract class AbstractChessman {
    //棋子類别
    protected String chess;
    //棋子坐标
    protected int x;
    protected int y;
    //構造方法
    public AbstractChessman(String chess){
        this.chess = chess;
    }
    //坐标設定
    public abstract void point(int x,int y);
    //顯示棋子資訊
    public void show(){
        System.out.println(this.chess+"("+this.x+","+this.y+")");
    }
}           

完善後的黑子類:

/**
 * 需求:黑子類
 */
public class BlackChessman extends AbstractChessman {
    /*
     * 構造方法,初始化黑棋子
     */
    public BlackChessman(){
        super("●");
        System.out.println("--一顆黑棋子誕生了!--");
    }
    /*
     * 重寫方法
     */
    @Override
    public void point(int x, int y) {
        this.x = x;
        this.y = y;
        this.show();
    }
}           

完善後的白子類為:

/**
 * 需求:白棋子
 */
public class WhiteChessman extends AbstractChessman {
    /*
     * 構造方法,初始化黑棋子
     */
    public WhiteChessman(){
        super("○");
        System.out.println("--一顆白棋子誕生了!--");
    }
    /*
     * 重寫方法
     */
    @Override
    public void point(int x, int y) {
        this.x = x;
        this.y = y;
        this.show();
    }
}           

在棋子工廠中不需要進行任何改變,因為在棋子工廠中我們獲得的是共享對象,外蘊狀态(位置資訊)是需要在用戶端進行設定的。

用戶端:

public class Test {
    public static void main(String[] args) {
        //建立工廠
        ChessmanFactory chessmanFactory = ChessmanFactory.getInstance();
        //随機數,用于生成棋子對象
        Random random = new Random();
        int radom = 0;
        AbstractChessman abstractChessman = null;
        //随機獲得棋子
        for (int i = 0; i < 10; i++) {
            radom = random.nextInt(2);
            switch (radom) {
            case 0:
                //獲得黑棋子
                abstractChessman = chessmanFactory.getChessmanObject('B');
                break;
            case 1:
                //獲得黑棋子
                abstractChessman = chessmanFactory.getChessmanObject('W');
                break;
            }
            if (abstractChessman!=null) {
                abstractChessman.point(i, random.nextInt(15));
            }
        }
    }
}           

需要注意的是,為了保證對象被共享,外蘊狀态需要儲存在用戶端,也就是說,x、y值在用戶端儲存。

享元對象特點

享元模式的重點在于“共享對象執行個體,降低記憶體空間”,實作享元模式時,需要規劃共享對象的粒度,才能降低記憶體空間,提高系統性能;例如如果将上邊的x、y轉為内蘊狀态,那麼享元工廠将儲存非常多的對象,失去享元模式的意義。

當系統中某個對象類型的執行個體較多且耗費大量記憶體,并且對象執行個體分類較少,且其部分狀态可以轉為外蘊狀态時,可以使用享元模式。單例模式本身也是一種特殊的享元模式,最大差別是單例模式的類不能被執行個體化,而享元模式類可以被執行個體化。

享元模式的缺點是,為了使對象可以共享,可能會将一些狀态轉為外蘊狀态,使得程式複雜性增加。

Java中的享元模式

Integer類對于經常使用的-128到127範圍内的Integer對象,當類被加載時就被建立了,并儲存在cache數組中,一旦程式調用valueOf方法,如果i的值是在-128到127之間就直接在cache緩存數組中去取Integer對象而不是建立一個新對象,這就是享元模式的應用。

public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }           

String類也應用了享元模式,常量池中維護的字元串可以被多個指針引用。

參考:《設計模式那點事》