常用設計模式系列(十二)—享元模式
第一節
前言
昏昏沉沉的兩天過去了,也不知道為什么,突然總覺得很困,可能之前熬夜熬的多了,所以現在可能年紀大了,需要蹦一蹦才能把自己從頹廢的邊緣拉扯回來,伸出你的手,讓我們向于和偉老師學習,來個養生迪(斗圖時刻)。
蹦完有沒有覺得賊精神,精神恢復,我們繼續講解我們的結構型設計模式,今天講解結構型設計模式第五章節—“享元模式”,“享元”和“外觀”一樣讓人琢磨不透,就像你追你愛慕的女孩的時候,和在一起之后的對比,雖然兩段關系不太一樣,但是她的心思,那可不是你能一眼猜透的;如何破冰?還是要深入了解,才能更有效的掌握核心信息。那么,“享元”如何代入呢?
可以這么理解,將一個系統中共同的內容抽象出來,多個使用者共用,減少共用內容的冗余,例如:現在國家倡導綠色出行,十個人都擁有自己的私家車,但是國家提供地鐵,可以運輸幾百個人,這樣把大家的交通工具抽象出來,抽象為公共交通,減少了街上車子的數量,減少了道路上車子的冗余,這個模式就是享元模式,“將公共元素進行抽象,然后共享這些公共元素”即享元。
第二節
享元模式概念
享元模式(Flyweight
Pattern)主要用于減少創建對象的數量,以減少內存占用和提高性能。這種類型的設計模式屬于結構型模式,它提供了減少對象數量從而改善應用所需的對象結構的方式,享元模式嘗試重用現有的同類對象,如果未找到匹配的對象,則創建新對象。是不是聽起來有點像單例模式?
1.實現方式不一樣,單例是一個類只有一個唯一的實例,而享元可以有多個實例,只是通過一個共享容器來存儲不同的對象。
2.使用場景不一樣,單例是強調減少實例化提升性能,因此一般用于一些需要頻繁創建和銷毀實例化對象或創建和銷毀實例化對象非常消耗資源的類中,如連接池、線程池。而享元則是強調共享相同對象或對象屬性,節約內存使用空間。
在系統運行時有大量對象的情況下,有可能會造成堆內存溢出,我們把其中共同的部分抽象出來,抽象為公用對象,如果有相同的業務請求,直接返回在內存中已有的對象,避免重新創建。我們常用的線程池、數據庫連接池、常量池等等相關,防止堆內存中對象被重復多次的無限制的創建,進行高效利用,哪里需要往哪里搬。
目前在我們身邊,共享的產品已經滲入我們身邊,共享單車、共享汽車、共享充電寶,當共享產品的廠家提供了共享產品時,就意味著這個產品開始了它的輪班生涯,例如共享廠家提供了一個充電寶,我們從這個充電寶的角度去看,第一次用充電寶,如果沒有,商家補貨,有了就拿走用,這個充電寶屬于共享池中,每個廠家規定的派發出去的也是有數量的,不能一直無限的擴展,就意味著池子的大小有限制,如果全部派發出去了,那你就需要等別人還回來才能使用,這些共享產品都統一管理起來在一個池子進行分配,達到共享的目的,這個模式就是享元模式。
此圖中抽象充電寶屬于抽象享元角色,充電寶A和B是具體享元角色,品牌屬于不需要抽象的,傳遞給享元角色使用,則為非享元角色,充電寶池則為享元工廠。
第三節
代碼實現
1.創建非享元對象-品牌
package com.yang.flyweight;/*** @ClassName Power* @Description 非享元對象——品牌* @Author IT小白架構師之路* @Date 2020/12/24* @Version 1.0**/
public class Version {//品牌名稱private String name;public Version(String name){this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
2.創建充電寶抽象對象
package com.yang.flyweight;/*** @ClassName AbstractRecharge* @Description 抽象充電寶對象* @Author IT小白架構師之路* @Date 2020/12/24* @Version 1.0**/
public abstract class AbstractRecharge {//品牌屬性protected Version version;//狀態 0閑置 1使用中private String status;public String getStatus() {return status;}public void setStatus(String status) {this.status = status;}public AbstractRecharge(String status,Version version){this.status = status;this.version = version;}/*** 充電方法*/public abstract void recharge();
}
3.創建A類充電寶
package com.yang.flyweight;/*** @ClassName Recharge* @Description 充電寶類A* @Author IT小白架構師之路* @Date 2020/12/24* @Version 1.0**/
public class RechargeA extends AbstractRecharge{public RechargeA(String status,Version version){super(status,version);}/*** 充電方法*/public void recharge(){System.out.println("我是"+super.version.getName()+"充電寶,我可以充電");}
}
4.創建B類充電寶
package com.yang.flyweight;/*** @ClassName RechargeB* @Description B類* @Author IT小白架構師之路* @Date 2020/12/24* @Version 1.0**/
public class RechargeB extends AbstractRecharge{public RechargeB(String status,Version version){super(status,version);}/*** 充電方法*/public void recharge(){System.out.println("我是"+super.version.getName()+"充電寶,我可以充電");}
}
5.創建充電寶池(類似數據庫連接池的方式,自動擴容)
package com.yang.flyweight;import java.util.ArrayList;
import java.util.List;/*** @ClassName RechargeFactory* @Description 充電寶池子* @Author IT小白架構師之路* @Date 2020/12/24* @Version 1.0**/
public class RechargeFactory {/*** 充電寶池子最大數量*/private static int maxNum = 5;/*** 初始化數量*/private static int initNum = 2;private static List<AbstractRecharge> list = new ArrayList<AbstractRecharge>();static {for (int i=0;i<initNum;i++){if(i%2==0){Version version = new Version("A品牌");list.add(new RechargeA("0",version));}else{Version version = new Version("B品牌");list.add(new RechargeB("0",version));}}System.out.println("充電寶數量初始化成功");}//獲取對象public static AbstractRecharge getRecharge() throws Exception{//總數大小int size = list.size();System.out.print("充電寶總數大小"+size+",");//查看閑置大小getHasNum();//是否需要等待boolean wait = true;while (true){boolean haveStatus = false;for (AbstractRecharge recharge : list){//有閑置的對象,直接返回if("0".equals(recharge.getStatus())){haveStatus = true;wait = false;recharge.setStatus("1");System.out.println("有可用充電寶,請取走");return recharge;}}if(!haveStatus){if (list.size()>=maxNum){System.out.println("目前無充電寶可用,等待2s");Thread.sleep(2000);}else{//新增對象并返回AbstractRecharge recharge;if (size%2==0){Version version = new Version("A品牌");recharge = new RechargeA("0",version);}else{Version version = new Version("B品牌");recharge = new RechargeB("0",version);}wait = false;recharge.setStatus("1");list.add(recharge);return recharge;}}}}/*** 歸還充電寶* @param recharge*/public static void backRecharge(AbstractRecharge recharge){//置閑recharge.setStatus("0");System.out.println("充電結束,充電寶已歸還");}/*** 獲取閑置數量*/public static void getHasNum() {int num = 0;for (AbstractRecharge recharge : list){//有閑置的對象,直接返回if("0".equals(recharge.getStatus())){num++;}}System.out.println("目前閑置數量"+num);}
}
6.創建runbale用來測試,執行充電方法
package com.yang.flyweight;/*** @ClassName RechaegeRunnable* @Description 注釋* @Author IT小白架構師之路* @Date 2020/12/24* @Version 1.0**/
public class RechargeRunnable implements Runnable{//傳入充電寶對象private AbstractRecharge recharge;private int i;public RechargeRunnable(AbstractRecharge recharge,int i){this.recharge = recharge;this.i = i;}@Overridepublic synchronized void run() {try {Thread.sleep(5000);//調用充電方法recharge.recharge();int num = i+1;//歸還充電寶RechargeFactory.backRecharge(recharge);System.out.println("第"+num+"個線程執行完畢");}catch (Exception e){e.printStackTrace();}}
}
- 創建客戶端測試,創建20個線程測試
package com.yang.flyweight;import java.util.ArrayList;
import java.util.List;/*** @ClassName Client* @Description 注釋* @Author IT小白架構師之路* @Date 2020/12/24* @Version 1.0**/
public class Client {public static void main(String[] args) throws Exception{List<Thread> threadList = new ArrayList<Thread>();//20個線程同時處理for (int i=0;i<20;i++){//獲取對象AbstractRecharge recharge = RechargeFactory.getRecharge();//創建新的線程去使用RechargeRunnable runnable = new RechargeRunnable(recharge,i);//啟動線程Thread thread = new Thread(runnable);thread.start();threadList.add(thread);}//設置當子線程處理完再執行主線程for (Thread temp:threadList){temp.join();}System.out.print("結束測試,");RechargeFactory.getHasNum();}
}
8.測試結果如下,使用享元模式對公用對象進行抽象
充電寶數量初始化成功
充電寶總數大小2,目前閑置數量2
有可用充電寶,請取走
充電寶總數大小2,目前閑置數量1
有可用充電寶,請取走
充電寶總數大小2,目前閑置數量0
充電寶總數大小3,目前閑置數量0
充電寶總數大小4,目前閑置數量0
充電寶總數大小5,目前閑置數量0
目前無充電寶可用,等待2s
目前無充電寶可用,等待2s
目前無充電寶可用,等待2s
我是A品牌充電寶,我可以充電
充電結束,充電寶已歸還
我是B品牌充電寶,我可以充電
充電結束,充電寶已歸還
第4個線程執行完畢
我是A品牌充電寶,我可以充電
充電結束,充電寶已歸還
第1個線程執行完畢
我是B品牌充電寶,我可以充電
充電結束,充電寶已歸還
第2個線程執行完畢
我是A品牌充電寶,我可以充電
充電結束,充電寶已歸還
第5個線程執行完畢
第3個線程執行完畢
有可用充電寶,請取走
充電寶總數大小5,目前閑置數量4
有可用充電寶,請取走
充電寶總數大小5,目前閑置數量3
有可用充電寶,請取走
充電寶總數大小5,目前閑置數量2
有可用充電寶,請取走
充電寶總數大小5,目前閑置數量1
有可用充電寶,請取走
充電寶總數大小5,目前閑置數量0
目前無充電寶可用,等待2s
目前無充電寶可用,等待2s
目前無充電寶可用,等待2s
我是A品牌充電寶,我可以充電
充電結束,充電寶已歸還
第6個線程執行完畢
我是B品牌充電寶,我可以充電
充電結束,充電寶已歸還
第7個線程執行完畢
我是B品牌充電寶,我可以充電
充電結束,充電寶已歸還
我是A品牌充電寶,我可以充電
充電結束,充電寶已歸還
我是A品牌充電寶,我可以充電
充電結束,充電寶已歸還
第10個線程執行完畢
第9個線程執行完畢
第8個線程執行完畢
有可用充電寶,請取走
充電寶總數大小5,目前閑置數量4
有可用充電寶,請取走
充電寶總數大小5,目前閑置數量3
有可用充電寶,請取走
充電寶總數大小5,目前閑置數量2
有可用充電寶,請取走
充電寶總數大小5,目前閑置數量1
有可用充電寶,請取走
充電寶總數大小5,目前閑置數量0
目前無充電寶可用,等待2s
目前無充電寶可用,等待2s
目前無充電寶可用,等待2s
我是A品牌充電寶,我可以充電
充電結束,充電寶已歸還
我是A品牌充電寶,我可以充電
充電結束,充電寶已歸還
我是B品牌充電寶,我可以充電
充電結束,充電寶已歸還
第12個線程執行完畢
我是A品牌充電寶,我可以充電
充電結束,充電寶已歸還
第13個線程執行完畢
我是B品牌充電寶,我可以充電
充電結束,充電寶已歸還
第11個線程執行完畢
第14個線程執行完畢
第15個線程執行完畢
有可用充電寶,請取走
充電寶總數大小5,目前閑置數量4
有可用充電寶,請取走
充電寶總數大小5,目前閑置數量3
有可用充電寶,請取走
充電寶總數大小5,目前閑置數量2
有可用充電寶,請取走
充電寶總數大小5,目前閑置數量1
有可用充電寶,請取走
我是A品牌充電寶,我可以充電
充電結束,充電寶已歸還
我是B品牌充電寶,我可以充電
充電結束,充電寶已歸還
第17個線程執行完畢
我是A品牌充電寶,我可以充電
充電結束,充電寶已歸還
第16個線程執行完畢
我是B品牌充電寶,我可以充電
充電結束,充電寶已歸還
第19個線程執行完畢
我是A品牌充電寶,我可以充電
充電結束,充電寶已歸還
第18個線程執行完畢
第20個線程執行完畢
結束測試,目前閑置數量5
第四節
享元模式優缺點及適用場景
優點:
1.相同對象可以進行管理,防止創建重復的對象導致內存溢出,這種方式提高了效率。
2.對象統一管理,需要做統一處理的時候則比較方便。
缺點:
1.為了使對象可以共享,需要將一些不能共享的狀態外部化,這將增加程序的復雜性。
2.讀取享元模式的外部狀態會使得運行時間稍微變長。
適用場景:
1.系統中存在大量相同或者相似對象,且占用較大的內存資源,優化方案可采用享元。
2.大部分的對象可以按照內部狀態進行分組,且可將不同的部分分割為外部對象的時候。
3.線程池、數據庫連接池等相關場景。
掃描二維碼
關注我吧
IT小白架構師之路