您好,登錄后才能下訂單哦!
本篇內容主要講解“Java的享元模式是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Java的享元模式是什么”吧!
前言
試想一下,如果我們要用程序來設計圍棋游戲,黑子181枚,白子180枚,那我們是不是每下一個子時,都要去new一個棋子對象呢?Java是一門面向對象語言,我們都知道如果在內存中不停的new新對象時,當對象數量太多時,又回收不及時時,將導致運行代價過高,帶來性能下降等問題。那我們怎么去解決這類問題呢?下面,將為大家講解本篇的重點,Java設計模式之——享元模式。
什么是享元模式
為了方便理解,我們先來看一下享元模式的兩種狀態:
內部狀態(Intrinsic State):是存儲在享元對象內部并且不會隨環境改變而改變的狀態,因此內部狀態可以共享。
外部狀態(Extrinsic State):是隨環境改變而改變的、不可以共享的狀態。享元對象的外部狀態必須由客戶端保存,并在享元對象被創建之后,在需要使用的時候再傳入到享元對象內部。一個外部狀態與另一個外部狀態之間是相互獨立的。
享元模式將一個對象的狀態分為內部狀態和外部狀態,其中,二者是相互獨立的,共享相同的內部狀態,通過設置不同的外部狀態來改變對象的特征,讓一個對象擁有不同的特征,但內部狀態始終是共享的,不可改變的。也就是,改變外部狀態不會引起內部狀態改變。
可以把圍棋想象成享元模式,他們的大小、形狀、顏色是內部狀態,棋子的位置是外部狀態,這樣在設計時,只需要設置黑白棋子兩個對象,黑棋共享黑色的內部狀態,白棋共享白色的內部狀態,棋盤上每個棋子的位置就是他們的外部狀態,圍棋盤361個交叉點位置,棋子每落一個位置(外部狀態),都不會改變棋子的顏色(內部狀態)。這樣是不是好理解一點。
享元模式一般會結合工廠模式使用,目的是為了創建一個享元工廠來負責維護享元池(Flyweight Pool),享元池里存放的是具有相同內部狀態的享元對象。在實際的日常業務的千變萬化中,能夠共享的內部狀態是很少的,所以享元對象一般都設計為較小的對象,包含的內部狀態也很少,這種對象也成為細粒度對象。
現在我們來看一下享元模式的英文定義:
Flyweight Pattern: Use sharing to support large numbers of fine-grained objects efficiently.
翻譯過來就是:運用共享技術有效地支持大量細粒度對象的復用。(Flyweight我不也不懂為什么國內都翻譯成享元,沒找到資料,可能是根據這個模式的作用和特性翻譯來的,如果有知道的朋友煩請文末留言告知一聲,謝謝!)
再看一下國內對享元模式的解釋:
享元模式(Flyweight Pattern):運用共享技術有效地支持大量細粒度對象的復用。系統只使用少量的對象,而這些對象都很相似,狀態變化很小,可以實現對象的多次復用。由于享元模式要求能夠共享的對象必須是細粒度對象,因此它又稱為輕量級模式,它是一種對象結構型模式。
簡而言之:享元模式的目的就是通過共享不變的部分,達到減少對象數量并節約內存的目的。
享元模式的四個角色
Flyweight(抽象享元類):接口或抽象類,聲明公共方法,這些方法可以向外界提供對象的內部狀態,設置外部狀態。
ConcreteFlyweight(具體享元類):實現了抽象享元類,其實例稱為享元對象。必須是可共享的,需要封裝享元對象的內部狀態;。
UnsharedConcreteFlyweight(非共享具體享元類):非共享的享元實現對象,并不是所有的享元對象都可以共享,非共享的享元對象通常是享元對象的組合對象。
FlyweightFactory(享元工廠類):享元工廠,主要用來創建并管理共享的享元對象,并對外提供訪問共享享元的接口。它針對抽象享元類編程,將各種類型的具體享元對象存儲在一個享元池中,享元池一般設計為一個存儲“鍵值對”的集合(也可以是其他類型的集合),可以結合工廠模式進行設計;當用戶請求一個具體享元對象時,享元工廠提供一個存儲在享元池中已創建的實例或者創建一個新的實例(如果不存在的話),返回新創建的實例并將其存儲在享元池中。
享元模式的UML圖
代碼實例
我就不用我和李大爺下棋的例子了,以免在他老大(幼小)的心靈上留下創傷。關于棋子的案例,網上也有很多版本,大家感興趣的可以自己去看。下面我們用王者榮耀游戲來舉例。我們知道,在一局對戰賽里,每隔幾分鐘就會出現一波小兵和超級兵,小兵都長的一模一樣,超級兵也是,如果王者團隊在設計小兵出場的時候,每出來一個小兵,就new一個小兵對象,那么在這個幾百萬甚至更多人同時在線角逐的游戲里,服務器壓力根本就頂不住,還能不能好好的、流暢的、愉快的上分了,小學生放學后早就乖乖在家做作業了。
那么怎樣設計呢?我們可以將小兵的體征、裝配、兵種作為內部狀態,然后它們在地圖上出擊的方向作為外部狀態,這樣無論小兵從哪個方向出擊(外部狀態怎樣改變),都不會改變小兵的體征和兵種(內部狀態),這樣我們在開發時,每個兵種只要有一個享元對象就可以了。來看代碼:
1、編寫抽象享元類
package com.weiya.mazhichu.designpatterns.flyweight; /** * <p class="detail"> * 功能:抽象享元類 * </p> * * @author Moore * @ClassName Soldier flyweight. * @Version V1.0. * @date 2019.09.03 21:06:52 */ public interface SoldierFlyweight { /** * <p class="detail"> * 功能:敵軍出擊方法 * </p> * * @param direction : * @author Moore * @date 2019.09.03 21:06:52 */ public void attack(String direction); }
2、編寫具體享元類
package com.weiya.mazhichu.designpatterns.flyweight; /** * <p class="detail"> * 功能:具體享元類 * </p> * * @author Moore * @ClassName Concrete solider flyweight. * @Version V1.0. * @date 2019.09.04 09:45:41 */ public class ConcreteSoliderFlyweight implements SoldierFlyweight { // 內部狀態 private String soliderType; public ConcreteSoliderFlyweight(String soliderType) { this.soliderType = soliderType; } @Override public void attack(String direction) { if("normal".equals(soliderType)){ System.out.println("普通兵加入戰場"); } if("super".equals(soliderType)){ System.out.println("超級兵加入戰場"); } System.out.println("出擊方向:"+direction); } }
3、編寫享元工廠
package com.weiya.mazhichu.designpatterns.flyweight; import java.util.HashMap; import java.util.Map; /** * <p class="detail"> * 功能:享元工廠 * </p> * * @author Moore * @ClassName Soldier fly weight factory. * @Version V1.0. * @date 2019.09.03 21:06:58 */ public class SoldierFlyWeightFactory { //工廠實例 private static SoldierFlyWeightFactory INSTANCE; // 享元池 private static Map<String,SoldierFlyweight> soldierMap = new HashMap<String,SoldierFlyweight>(); private SoldierFlyWeightFactory(){ SoldierFlyweight normalSoldier = new ConcreteSoliderFlyweight("normal"); soldierMap.put("normal",normalSoldier); SoldierFlyweight superSolider = new ConcreteSoliderFlyweight("super"); soldierMap.put("super",superSolider); } /** * <p class="detail"> * 功能:獲取工廠實例 * </p> * * @return soldier fly weight factory * @author Moore * @date 2019.09.03 21:07:02 */ public static SoldierFlyWeightFactory getInstance(){ if(INSTANCE == null){ INSTANCE = new SoldierFlyWeightFactory(); return INSTANCE; } return INSTANCE; } /** * <p class="detail"> * 功能:獲取享元對象 * </p> * * @param soliderType : * @return soldier flyweight * @author Moore * @date 2019.09.03 21:07:02 */ public SoldierFlyweight getSolider(String soliderType){ return soldierMap.get(soliderType); } /** * <p class="detail"> * 功能:獲取享元池對象數量 * </p> * * @return int * @author Moore * @date 2019.09.03 21:07:02 */ public int getSoliderSize(){ return soldierMap.size(); } }
4、客戶端測試
package com.weiya.mazhichu.designpatterns.flyweight; /** * <p class="detail"> * 功能: * </p> * * @author Moore * @ClassName Honour of kings test. * @Version V1.0. * @date 2019.09.03 21:06:44 */ public class HonourOfKingsTest { public static void main(String[] args) { System.out.println("敵軍還有五秒到達戰場!"); SoldierFlyWeightFactory factory = SoldierFlyWeightFactory.getInstance(); SoldierFlyweight soldier1 = factory.getSolider("normal"); SoldierFlyweight soldier2 = factory.getSolider("normal"); SoldierFlyweight soldier3 = factory.getSolider("normal"); soldier1.attack("上路"); soldier2.attack("中路"); soldier3.attack("下路"); System.out.println(soldier1 == soldier2); System.out.println(soldier2 == soldier3); System.out.println("--------------------------"); System.out.println("主宰已被擊敗!"); SoldierFlyweight soldier4 = factory.getSolider("super"); SoldierFlyweight soldier5 = factory.getSolider("super"); SoldierFlyweight soldier6 = factory.getSolider("super"); soldier4.attack("上路"); soldier5.attack("中路"); soldier6.attack("下路"); System.out.println("對方法師殘血,被超級兵打死..."); System.out.println(soldier4 == soldier5); System.out.println(soldier5 == soldier6); System.out.println("--------------------------"); System.out.println("該案例一共生成對象:">
查看運行結果:
可以看出,我們一共派出了6個小兵,其中3個普通兵,3個超級兵,但是享元池中只有兩個對象(一個普通兵、一個超級兵對象),也就是說,無論派出多少普通兵或者超級兵,無論它們要從哪一路出擊,都不會影響兵的內部狀態,從而讓整個系統的對象大大減少,減少內存消耗,不卡就不影響游戲體驗,小學生又可以開心快樂的出來坑人了,但是要以學業為重哦!
享元模式擴展
在上面的實例中,我們主要講的是具體的享元對象,也就是所有的享元對象都是必須共享的。但是享元模式的四個角色中還有一個非共享的享元實現對象,什么意思呢,顧名思義就是享元對象不一定要共享,但是它通常是作為享元對象的組合對象來使用。從這個層面來說,我們又把享元對象分為:
單純享元模式:在單純享元模式中,所有的享元對象都是可以共享的,即所有抽象享元類的子類都可共享,不存在非共享具體享元類。
復合享元模式:將一些單純享元使用組合模式加以組合,可以形成復合享元對象,這樣的復合享元對象本身不能共享,但是它們可以分解成單純享元對象,而后者則可以共享。(復合的享元對象實現了抽象享元類,它的實例就是非共享的享元實現對象)
復合享元模式中,組成復合享元對象的每個單純享元對象擁有自己的內部狀態,而每個單純享元對象的外部狀態都和復合享元對象的外部狀態相同。所以復合享元模式可以對多個單純享元對象設置相同的外部狀態, 這也是復合享元模式的應用場景。
單純的享元模式我就不再贅述了,看上面的棋子或者農藥的實例,下面主要說一下組合享元模式,以及它為何非共享,來看代碼:
1、編寫復合享元角色類
package com.weiya.mazhichu.designpatterns.flyweight; import java.util.HashMap; import java.util.Map; /** * <p class="detail"> * 功能: 復合享元角色類(非共享享元實現對象) * </p> * * @author Moore * @ClassName Concrete composite solider flyweight. * @Version V1.0. * @date 2019.09.04 10:56:11 */ public class ConcreteCompositeSoliderFlyweight implements SoldierFlyweight { private static Map<String,SoldierFlyweight> soldierMap = new HashMap<String,SoldierFlyweight>(); /** * <p class="detail"> * 功能: 增加單純享元對象 * </p> * * @param soliderType : * @param flyweight : * @author Moore * @date 2019.09.04 10:56:11 */ public void add(String soliderType,SoldierFlyweight flyweight){ soldierMap.put(soliderType,flyweight); } /** * <p class="detail"> * 功能: flyWeights是單純享元對象的集合,它們具有相同的外部狀態extrinsicState, * 調用的時候使用循環調用單純享元對象的attack方法 * </p> * * @param direction : * @author Moore * @date 2019.09.03 21:06:52 */ @Override public void attack(String direction) { SoldierFlyweight flyweight = null; for(String str : soldierMap.keySet()){ flyweight = soldierMap.get(str); flyweight.attack(direction); } } /** * 移除單純享元對象. * @param soliderType */ private void remove(String soliderType) { soldierMap.remove(soliderType); } }
2、修改后的享元工廠角色類
package com.weiya.mazhichu.designpatterns.flyweight; import java.util.HashMap; import java.util.List; import java.util.Map; /** * <p class="detail"> * 功能:享元工廠 * </p> * * @author Moore * @ClassName Soldier fly weight factory. * @Version V1.0. * @date 2019.09.03 21:06:58 */ public class SoldierFlyWeightFactory { //工廠實例 private static SoldierFlyWeightFactory INSTANCE; // 享元池 private static Map<String,SoldierFlyweight> soldierMap = new HashMap<String,SoldierFlyweight>(); private SoldierFlyWeightFactory(){ SoldierFlyweight normalSoldier = new ConcreteSoliderFlyweight("normal"); soldierMap.put("normal",normalSoldier); SoldierFlyweight superSolider = new ConcreteSoliderFlyweight("super"); soldierMap.put("super",superSolider); } /** * <p class="detail"> * 功能:獲取工廠實例 * </p> * * @return soldier fly weight factory * @author Moore * @date 2019.09.03 21:07:02 */ public static SoldierFlyWeightFactory getInstance(){ if(INSTANCE == null){ INSTANCE = new SoldierFlyWeightFactory(); return INSTANCE; } return INSTANCE; } /** * <p class="detail"> * 功能:獲取享元對象(單純享元工廠方法) * </p> * * @param soliderType : * @return soldier flyweight * @author Moore * @date 2019.09.03 21:07:02 */ public SoldierFlyweight getSolider(String soliderType){ return soldierMap.get(soliderType); } /** * <p class="detail"> * 功能:復合享元工廠方法 * </p> * * @param compositeSoliderTypes : * @return soldier flyweight * @author Moore * @date 2019.09.04 11:06:24 */ public SoldierFlyweight getCompositeSolider(List<String> compositeSoliderTypes){ ConcreteCompositeSoliderFlyweight compositeFlyweight = new ConcreteCompositeSoliderFlyweight(); for(String soliderType : compositeSoliderTypes){ compositeFlyweight.add(soliderType,this.getSolider(soliderType)); } return compositeFlyweight; } /** * <p class="detail"> * 功能:獲取享元池對象數量 * </p> * * @return int * @author Moore * @date 2019.09.03 21:07:02 */ public int getSoliderSize(){ return soldierMap.size(); } }
3、編寫測試類
package com.weiya.mazhichu.designpatterns.flyweight; import java.util.ArrayList; import java.util.List; /** * <p class="detail"> * 功能: 測試單純享元模式和復合享元模式 * </p> * * @author Moore * @ClassName Flyweight test. * @Version V1.0. * @date 2019.09.04 11:08:51 */ public class FlyweightTest { public static void main(String[] args) { SoldierFlyWeightFactory factory = SoldierFlyWeightFactory.getInstance(); String soliderType = "normal"; SoldierFlyweight soldierFlyweight1 = factory.getSolider(soliderType); SoldierFlyweight soldierFlyweight2 = factory.getSolider(soliderType); soldierFlyweight1.attack("上路"); soldierFlyweight2.attack("中路"); System.out.println("---------------------------------"); List<String> compositeSoliderType = new ArrayList<String>(); compositeSoliderType.add("normal"); compositeSoliderType.add("super"); compositeSoliderType.add("normal"); compositeSoliderType.add("super"); compositeSoliderType.add("normal"); SoldierFlyweight compositeSoliderFlyeweight1 = factory.getSolider(compositeSoliderType); SoldierFlyweight compositeSoliderFlyeweight2 = factory.getSolider(compositeSoliderType); compositeSoliderFlyeweight1.attack("上路"); compositeSoliderFlyeweight2.attack("中路"); System.out.println("---------------------------------"); System.out.println("單純享元模式是否共享對象:">
查看運行結果:
結合運行結果,再來逐字逐句看一下這一段,你應該就能有所體會了。
復合享元模式中,組成復合享元對象的每個單純享元對象擁有自己的內部狀態,而每個單純享元對象的外部狀態都和復合享元對象的外部狀態相同。所以復合享元模式可以對多個單純享元對象設置相同的外部狀態, 這也是復合享元模式的應用場景。
復合享元模式UML圖
享元模式總結
使用場景
系統有大量相似或者相同對象。由于這類對象的大量使用,造成內存的大量耗費。
需要緩沖池的場景,(享元池,也就是在需要多次使用享元對象的時候)。
對象的大部分狀態都可以外部化,可以將這些外部狀態傳入對象中。
優點
大大減少對象的創建,降低系統的內存,使效率提高。
享元模式的外部狀態相對獨立,而且不會影響其內部狀態,從而使得享元對象可以在不同的環境中被共享。
缺點
需要分離出外部狀態和內部狀態,提高了系統的復雜度。
讀取享元模式的外部狀態會使得運行時間稍微變長。
到此,相信大家對“Java的享元模式是什么”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。