享元模式采用共享機制來避免大量擁有相同內(nèi)容對象的開銷。這種開銷最常見搔预、最直觀的就是內(nèi)存的損耗霹期。享元對象能做到共享的關(guān)鍵是區(qū)分內(nèi)蘊狀態(tài)(Internal State)和外蘊狀態(tài)(External State)。
- 內(nèi)蘊狀態(tài)是存儲在享元對象內(nèi)部的拯田,并且是不會隨環(huán)境的改變而有所不同历造。因此,一個享元可以具有內(nèi)蘊狀態(tài)并可以共享船庇。
- 外蘊狀態(tài)是隨環(huán)境的改變而改變的吭产、不可以共享的。享元對象的外蘊狀態(tài)必須由客戶端保存溢十,并在享元對象被創(chuàng)建之后垮刹,在需要使用的時候再傳入到享元對象內(nèi)部达吞。外蘊狀態(tài)不可以影響享元對象的內(nèi)蘊狀態(tài)张弛,它們是相互獨立的。
組成結(jié)構(gòu)
享元模式一般有三個角色:
- 抽象享元(Flyweight)角色 :給出一個抽象接口酪劫,以規(guī)定出所有具體享元角色需要實現(xiàn)的方法吞鸭。
- 具體享元(ConcreteFlyweight)角色:實現(xiàn)抽象享元角色所規(guī)定出的接口。如果有內(nèi)蘊狀態(tài)的話覆糟,必須負(fù)責(zé)為內(nèi)蘊狀態(tài)提供存儲空間刻剥。
- 享元工廠(FlyweightFactory)角色 :本角色負(fù)責(zé)創(chuàng)建和管理享元角色。本角色必須保證享元對象可以被系統(tǒng)適當(dāng)?shù)毓蚕硖沧帧.?dāng)一個客戶端對象調(diào)用一個享元對象的時候造虏,享元工廠角色會檢查系統(tǒng)中是否已經(jīng)有一個符合要求的享元對象御吞。如果已經(jīng)有了,享元工廠角色就應(yīng)當(dāng)提供這個已有的享元對象漓藕;如果系統(tǒng)中沒有一個適當(dāng)?shù)南碓獙ο蟮脑捥罩椋碓S角色就應(yīng)當(dāng)創(chuàng)建一個合適的享元對象。
簡單應(yīng)用
以象棋游戲為例享钞,假設(shè)我們把每顆棋子看成是一個對象揍诽,那么每開啟一個棋局需要創(chuàng)建32個棋子對象,那如果同時存在一百萬個棋局的話就很可怕了@跏J畲唷!我們試著用享元模式解決這個問題狐肢。
首先劃分外蘊狀態(tài)和內(nèi)蘊狀態(tài)
外蘊狀態(tài):不同的棋子角色是不確定的添吗,創(chuàng)建棋子的時候才會確認(rèn)是什么角色。
內(nèi)蘊狀態(tài):棋子的形狀和大小基本是不會變化的份名,不會隨著棋子角色變化而變化根资。
1、抽象享元(Flyweight)角色
定義一個創(chuàng)建棋子的接口
public interface IChess {
/**
* 棋子信息
*/
void info();
}
2同窘、具體享元(ConcreteFlyweight)角色
實現(xiàn)棋子的創(chuàng)建并打印棋子信息
public class Chess implements IChess {
public static final String TAG = "Chess";
//可變
private String role; //棋子角色
//不可變
private String shape = "CIRCLE"; //棋子形狀
private int radius = 100; //棋子半徑大小
public Chess(String role) {
this.role = role;
}
@Override
public void info() {
Log.d(TAG, String.format("角色%s玄帕,形狀%s,大小%d", role, shape, radius));
}
}
3想邦、享元工廠(FlyweightFactory)角色
負(fù)責(zé)創(chuàng)建棋子裤纹,使用HashMap保存已創(chuàng)建的棋子達(dá)到復(fù)用目的
public class ChessFactory {
private static HashMap<String, Chess> chessHashMap = new HashMap<>(); //負(fù)責(zé)存儲共享對象
//如果共享Map內(nèi)已經(jīng)存在role角色的棋子,直接復(fù)用丧没;否則創(chuàng)建新棋子
public static Chess getChess(String role) {
Chess chess = chessHashMap.get(role);
if (chess == null) {
Log.d(TAG, "=================創(chuàng)建一個新的棋子=================");
chess = new Chess(role);
chessHashMap.put(role, chess);
}
return chess;
}
}
隨機創(chuàng)建30個棋子(六種角色)鹰椒,查看程序運行結(jié)果:
public void button(View view) {
String[] roles = {"將", "帥", "車", "馬", "炮", "兵"};
//創(chuàng)建30個棋子
for (int i = 0; i < 30; i++) {
ChessFactory.getChess(roles[(int) (Math.random() * 1000) % 6]).createChess();
}
}
2020-08-11 15:20:56.774 16675-16675/com.android.multidex D/Chess: =================創(chuàng)建一個新的棋子=================
2020-08-11 15:20:56.774 16675-16675/com.android.multidex D/Chess: 角色車,形狀CIRCLE呕童,大小100
2020-08-11 15:20:56.774 16675-16675/com.android.multidex D/Chess: =================創(chuàng)建一個新的棋子=================
2020-08-11 15:20:56.774 16675-16675/com.android.multidex D/Chess: 角色炮漆际,形狀CIRCLE,大小100
2020-08-11 15:20:56.774 16675-16675/com.android.multidex D/Chess: =================創(chuàng)建一個新的棋子=================
2020-08-11 15:20:56.775 16675-16675/com.android.multidex D/Chess: 角色馬夺饲,形狀CIRCLE奸汇,大小100
2020-08-11 15:20:56.775 16675-16675/com.android.multidex D/Chess: =================創(chuàng)建一個新的棋子=================
2020-08-11 15:20:56.775 16675-16675/com.android.multidex D/Chess: 角色兵,形狀CIRCLE往声,大小100
2020-08-11 15:20:56.775 16675-16675/com.android.multidex D/Chess: 角色炮擂找,形狀CIRCLE,大小100
2020-08-11 15:20:56.776 16675-16675/com.android.multidex D/Chess: 角色馬浩销,形狀CIRCLE贯涎,大小100
2020-08-11 15:20:56.776 16675-16675/com.android.multidex D/Chess: =================創(chuàng)建一個新的棋子=================
2020-08-11 15:20:56.776 16675-16675/com.android.multidex D/Chess: 角色將,形狀CIRCLE慢洋,大小100
2020-08-11 15:20:56.776 16675-16675/com.android.multidex D/Chess: 角色兵塘雳,形狀CIRCLE陆盘,大小100
2020-08-11 15:20:56.776 16675-16675/com.android.multidex D/Chess: 角色將,形狀CIRCLE败明,大小100
2020-08-11 15:20:56.776 16675-16675/com.android.multidex D/Chess: =================創(chuàng)建一個新的棋子=================
2020-08-11 15:20:56.777 16675-16675/com.android.multidex D/Chess: 角色帥礁遣,形狀CIRCLE,大小100
2020-08-11 15:20:56.777 16675-16675/com.android.multidex D/Chess: 角色帥肩刃,形狀CIRCLE祟霍,大小100
2020-08-11 15:20:56.777 16675-16675/com.android.multidex D/Chess: 角色車,形狀CIRCLE盈包,大小100
2020-08-11 15:20:56.777 16675-16675/com.android.multidex D/Chess: 角色帥沸呐,形狀CIRCLE,大小100
2020-08-11 15:20:56.778 16675-16675/com.android.multidex D/Chess: 角色車呢燥,形狀CIRCLE崭添,大小100
2020-08-11 15:20:56.778 16675-16675/com.android.multidex D/Chess: 角色將,形狀CIRCLE叛氨,大小100
2020-08-11 15:20:56.778 16675-16675/com.android.multidex D/Chess: 角色馬呼渣,形狀CIRCLE,大小100
2020-08-11 15:20:56.778 16675-16675/com.android.multidex D/Chess: 角色馬寞埠,形狀CIRCLE屁置,大小100
2020-08-11 15:20:56.779 16675-16675/com.android.multidex D/Chess: 角色帥,形狀CIRCLE仁连,大小100
2020-08-11 15:20:56.779 16675-16675/com.android.multidex D/Chess: 角色帥蓝角,形狀CIRCLE,大小100
2020-08-11 15:20:56.779 16675-16675/com.android.multidex D/Chess: 角色兵饭冬,形狀CIRCLE使鹅,大小100
2020-08-11 15:20:56.780 16675-16675/com.android.multidex D/Chess: 角色馬,形狀CIRCLE昌抠,大小100
2020-08-11 15:20:56.780 16675-16675/com.android.multidex D/Chess: 角色馬患朱,形狀CIRCLE,大小100
2020-08-11 15:20:56.780 16675-16675/com.android.multidex D/Chess: 角色炮炊苫,形狀CIRCLE裁厅,大小100
2020-08-11 15:20:56.780 16675-16675/com.android.multidex D/Chess: 角色將,形狀CIRCLE劝评,大小100
2020-08-11 15:20:56.780 16675-16675/com.android.multidex D/Chess: 角色將姐直,形狀CIRCLE,大小100
2020-08-11 15:20:56.781 16675-16675/com.android.multidex D/Chess: 角色馬蒋畜,形狀CIRCLE,大小100
2020-08-11 15:20:56.781 16675-16675/com.android.multidex D/Chess: 角色馬撞叽,形狀CIRCLE姻成,大小100
2020-08-11 15:20:56.781 16675-16675/com.android.multidex D/Chess: 角色車插龄,形狀CIRCLE,大小100
2020-08-11 15:20:56.781 16675-16675/com.android.multidex D/Chess: 角色馬科展,形狀CIRCLE均牢,大小100
從結(jié)果可以看出,每個角色的棋子都只有在第一次使用的時候需要創(chuàng)建才睹,也就是說棋子對象的個數(shù)只與角色數(shù)量有關(guān)徘跪。這樣就成功實現(xiàn)了對象的共享復(fù)用,減少了因重復(fù)創(chuàng)建相同內(nèi)容的對象帶來的內(nèi)存開銷琅攘。
優(yōu)缺點
優(yōu)點
- 極大的減少系統(tǒng)中對象的個數(shù)垮庐,降低內(nèi)存的消耗;
- 享元模式 的外部狀態(tài)相對獨立坞琴,而且不會影響其內(nèi)部狀態(tài)哨查,從而使得享元對象可以在不同的環(huán)境中被共享。
缺點
為了使對象可以共享剧辐,需要劃分內(nèi)蘊狀態(tài)和外蘊狀態(tài)寒亥,使得程序的設(shè)計變得復(fù)雜。
享元模式與對象池的區(qū)別
相同點:
享元模式和對象池的最終目標(biāo)是相同的荧关,都是為了減少對象的數(shù)量溉奕,減少內(nèi)存的使用。它們都是通過維護和共享一組對象實現(xiàn)對象的復(fù)用忍啤。
不同點:
享元模式是結(jié)構(gòu)型模式腐宋。它把可以變化的狀態(tài)剝離出來并對外提供接口,并且共享不變的東西檀轨。享元對外提供的接口常常會包含一個String類型的參數(shù)胸竞,通常參數(shù)與對象是一對一的關(guān)系。
而對象池是構(gòu)造型模式参萄,側(cè)重于提供整個對象實例卫枝。對調(diào)用者而言對象池提供的對象都沒有區(qū)別,這個可以用讹挎,那個也可以用校赤。