新聞中心
大家好,我是老田,今天我給大家分享設(shè)計(jì)模式中的享元模式。用貼切的生活故事,以及真實(shí)項(xiàng)目場(chǎng)景來講設(shè)計(jì)模式,最后用一句話來總結(jié)這個(gè)設(shè)計(jì)模式。

網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)建站!專注于網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、微信小程序定制開發(fā)、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了廣元免費(fèi)建站歡迎大家使用!
下面是本文目錄:
背景
享元模式(Flyweight Pattern)又叫作輕量級(jí)模式,是對(duì)象池的一種實(shí)現(xiàn)。
類似線程池,線程池可以避免不停地創(chuàng)建和銷毀多個(gè)對(duì)象,消耗性能。
享元模式提供了減少對(duì)象數(shù)量從而改善應(yīng)用所需的對(duì)象結(jié)構(gòu)的方式。
英文解釋:
Use sharing to support large numbers of fine-grained objects efficiently.
享元模式(Flyweight Pattern)其宗旨是共享細(xì)粒度對(duì)象,將多個(gè)對(duì)同一對(duì)象的訪問集中起來,不必為每個(gè)訪問者都創(chuàng)建一個(gè)單獨(dú)的對(duì)象, 主要用于減少創(chuàng)建對(duì)象的數(shù)量,以減少內(nèi)存占用和提高性能。
屬于結(jié)構(gòu)性設(shè)計(jì)模式,其中結(jié)構(gòu)性設(shè)計(jì)模式有:代理、門面、裝飾器、享元、橋接、適配器、組合。
注意:
享元模式把一個(gè)對(duì)象的狀態(tài)分成內(nèi)部狀態(tài)和外部狀態(tài),內(nèi)部狀態(tài)是不變的,外部狀態(tài)是變化的;然后通過共享不變的部分,達(dá)到減少對(duì)象數(shù)量并節(jié)約內(nèi)存的目的。
生活案例
房屋中介
只要是個(gè)城市,就少不了房屋中介,房屋中介存有大量的出租房屋信息,并且一家房屋中介往往會(huì)有多個(gè)門店,但是所有門店都共享這些房屋信息(共享的是出租房屋的信息)。
個(gè)人身份證信息
每個(gè)中國公民都有一張身份證,并且這張身份證信息在公安系統(tǒng)中是共享的,全國各公安局派出所都會(huì)共享你的身份證信息(共享的是個(gè)人身份信息)。
高考志愿填報(bào)
每所大學(xué)在每個(gè)省都有明確的招收名額,這些名額對(duì)于該省的所有高考生而言都是共享的(共享的是招收名額)。
圖書館
圖書館里的可借書籍,對(duì)多有讀者是共享的,大家都可以查詢此書是否已經(jīng)被借出去,還?;究山?共享的是圖書)。
....
簡單代碼實(shí)現(xiàn)
下面我們通過一個(gè)案例來演示享元模式(圖書館為例)。
- public interface Book {
- void borrow();
- }
- /**
- * @author java后端技術(shù)全棧
- */
- public class ConcreteBook implements Book {
- //被借出去的書名
- private String name;
- public ConcreteBook(String name) {
- this.name = name;
- }
- @Override
- public void borrow() {
- System.out.println("圖書館借出去一本書,書名:"+this.name);
- }
- }
- import java.util.HashMap;
- import java.util.Map;
- /** 圖書館
- * @author java后端技術(shù)全棧
- */
- public class Llibrary {
- private Map
bookMap = new HashMap<>(); - private Llibrary() {
- }
- //只能有一個(gè)圖書館
- public static Llibrary getInstance() {
- return LazyHolder.LAZY_STATIC_SINGLETON;
- }
- //通過書名name來借書
- public Book libToBorrow(String name) {
- Book book;
- //如果圖書館有,直接把書借走
- if (bookMap.containsKey(name)) {
- book = bookMap.get(name);
- } else {//圖書館沒有,則錄入一本書,然后把書借走
- book = new ConcreteBook(name);
- bookMap.put(name, book);
- }
- return book;
- }
- //返回還有多少本書
- public int bookSize() {
- return bookMap.size();
- }
- private static class LazyHolder {
- private static final Llibrary LAZY_STATIC_SINGLETON = new Llibrary();
- }
- }
- import java.util.ArrayList;
- import java.util.List;
- public class Student {
- private static List
bookList = new ArrayList<>(); - private static BookFactory bookFactory;
- public static void main(String[] args) {
- bookFactory = BookFactory.getInstance();
- studenBorrow("java 從入門到精通");
- studenBorrow("java 從入門到放棄");
- studenBorrow("JVM java虛擬機(jī)");
- studenBorrow("java編程思想");
- //還了后,再借一次
- studenBorrow("java 從入門到精通");
- studenBorrow("java 從入門到放棄");
- studenBorrow("JVM java虛擬機(jī)");
- studenBorrow("java編程思想");
- //還了后,再借一次
- studenBorrow("java 從入門到精通");
- studenBorrow("java 從入門到放棄");
- studenBorrow("JVM java虛擬機(jī)");
- studenBorrow("java編程思想");
- //把每一本書借出去
- for (Book book:bookList){
- book.borrow();
- }
- System.out.println("學(xué)生一共借了 "+bookList.size()+"本書");
- System.out.println("學(xué)生一共借了 "+ bookFactory.bookSize()+"本書");
- }
- private static void studenBorrow(String name) {
- bookList.add(bookFactory.libToBorrow(name));
- }
- }
運(yùn)行結(jié)果
- 圖書館借出去一本書,書名:java 從入門到精通
- 圖書館借出去一本書,書名:java 從入門到放棄
- 圖書館借出去一本書,書名:JVM java虛擬機(jī)
- 圖書館借出去一本書,書名:java編程思想
- 圖書館借出去一本書,書名:java 從入門到精通
- 圖書館借出去一本書,書名:java 從入門到放棄
- 圖書館借出去一本書,書名:JVM java虛擬機(jī)
- 圖書館借出去一本書,書名:java編程思想
- 圖書館借出去一本書,書名:java 從入門到精通
- 圖書館借出去一本書,書名:java 從入門到放棄
- 圖書館借出去一本書,書名:JVM java虛擬機(jī)
- 圖書館借出去一本書,書名:java編程思想
- 學(xué)生一共借了 12本書
- 學(xué)生一共借了 4本書
其實(shí),圖書館只有四本書,但是多個(gè)人借,A借來看完了,B再去借,B還了C再去借。
這些書籍就被大家共享了。
享元模式的UML類圖如下:
由上圖可以看到,享元模式主要包含3個(gè)角色。
- 抽象享元角色(Book):享元對(duì)象抽象基類或者接口,同時(shí)定義出對(duì)象的外部狀態(tài)和內(nèi)部狀態(tài)的接口或?qū)崿F(xiàn)。
- 具體享元角色(ConcreteBook):實(shí)現(xiàn)抽象角色定義的業(yè)務(wù)。該角色的內(nèi)部狀態(tài)處理應(yīng)該與環(huán)境無關(guān),不會(huì)出現(xiàn)一個(gè)操作改變內(nèi)部狀態(tài)、同時(shí)修改了外部狀態(tài)的情況。
- 享元工廠(BookFactory):負(fù)責(zé)管理享元對(duì)象池和創(chuàng)建享元對(duì)象。
也許這個(gè)例子你還是不太明白,下面我們就用工作中常見的場(chǎng)景來解釋一通。
大佬們是怎樣使用的
關(guān)于享元模式,在JDK中大量的使用,比如:String、Integer、Long等類中,都有使用到。
Integer中的享元模式
下面這段代碼輸出什么?
- /**
- * 歡迎關(guān)注公眾號(hào):java后端技術(shù)全棧
- *
- * @author 田維常
- * @date 2021/06/02 19:30
- */
- public class IntegerDemo {
- public static void main(String[] args) {
- Integer a = 100;
- Integer b = Integer.valueOf(100);
- System.out.println(a == b);
- Integer c = new Integer(1000);
- Integer d = Integer.valueOf(1000);
- System.out.println(c == d);
- }
- }
很多人可能會(huì)認(rèn)為輸出
- true
- true
其實(shí),非也,這里最終輸出的是:
- true
- false
為什么呢?100就可以比較,1000就不能比較了?
其實(shí),在Integer里就用到了享元模式,它就是把-128到127這個(gè)范圍的數(shù)據(jù)緩存起來(放在Integer類型的數(shù)組中)。
- static final int low = -128;
- public static Integer valueOf(int i) {
- //high默認(rèn)是127
- if (i >= IntegerCache.low && i <= IntegerCache.high)
- return IntegerCache.cache[i + (-IntegerCache.low)];
- return new Integer(i);
- }
下面進(jìn)行一個(gè)簡要的分析:
關(guān)于Integer的緩存,推薦看這篇文章:
這里Integer里的IntegerCache里就用到了享元模式。
關(guān)于Integer 推薦:面試官:說說Integer緩存范圍
String中的享元模式
Java中講String類定義為final不能繼承,并且將屬性value也定義為final便是不可變,JVM中字符串一般保存在字符串常量池中,Java會(huì)確保一個(gè)字符串在常量池中只會(huì)有一份拷貝,這個(gè)字符串常量池在JDK1.6中位于方法區(qū)(永久代)中,而JDK1.7以后,JVM講其從方法區(qū)移動(dòng)到了堆heap中。
下面這段代碼輸出什么?
- /**
- * 歡迎關(guān)注公眾號(hào):java后端技術(shù)全棧
- *
- * @author 田維常
- * @date 2021/06/03
- */
- public class StringDemo {
- public static void main(String[] args) throws Exception {
- String s1 = "abcd";
- String s2 = "abcd";
- String s3 = "ab" + "cd";
- String s4 = "ab" + new String("cd");
- String s5 = new String("abcd");
- String s6 = s5.intern();
- String s7 = "a";
- String s8 = "bcd";
- String s9 = s7 + s8;
- System.out.println("s1 == s2 " + (s1 == s2));
- System.out.println("s1 == s3 " + (s1 == s3));
- System.out.println("s1 == s4 " + (s1 == s4));
- System.out.println("s1 == s6 " + (s1 == s6));
- System.out.println("s1 == s9 " + (s1 == s9));
- System.out.println("s4 == s5 " + (s4 == s5));
- }
- }
String類中的value是final修飾的,以字面量的形式創(chuàng)建String變量時(shí),JVM會(huì)在編譯期間就把該字面量“abcd”放到字符串常量池匯總,有Java程序啟動(dòng)的時(shí)候就已經(jīng)加載到內(nèi)存中了。這個(gè)字符串常量的特點(diǎn)就是有且僅有一份相同的字面量,如果其他相同字面量,JVM則返回這個(gè)字面量的引用,如果沒有相同的字面量,則再字符串常量池中創(chuàng)建這個(gè)字面量并返回它的引用。
由于s2指向字面量"abcd"在常量池中已經(jīng)存在了(s1先于s2),于是JVM就返回這個(gè)字面量綁定的引用,所以s1==s2。
s3中字面量的拼接其實(shí)在JVM層已經(jīng)做了優(yōu)化,在JVM編譯期間就對(duì)s3的拼接做了優(yōu)化,所以s1、s2、s3都可以理解為是同一個(gè),即s1==s3。
s4中的new String("cd"),此時(shí)生成了兩個(gè)對(duì)象,"cd"和new String("cd"),"cd"存在于字符串常量池中,new String("cd")存在于堆heap中,String s4="ab"+ new String("cd");實(shí)質(zhì)上是兩個(gè)對(duì)象的相加,編譯器不會(huì)對(duì)其進(jìn)行優(yōu)化,相加的結(jié)果存在于堆heap中,而s2存在于字符串常量池中,當(dāng)然不相等,即s1!=s4。
s4和s5最終的結(jié)果都是在堆中,所以此時(shí)s4!=s5
s5.intern()方法能是一個(gè)維度對(duì)總的字符串在運(yùn)行期間動(dòng)態(tài)地加入到字符串常量池中(字符串常量池的內(nèi)容是程序啟動(dòng)的時(shí)候就以及酒精加載好了,如果字符串常量池中存在該對(duì)象對(duì)應(yīng)的字面量,則返回該字面量在字符串常量池中的引用,否則,創(chuàng)建復(fù)制一份該字面量到字符串常量池中并發(fā)那會(huì)它的引用),因此s1==s6。
s9是s7和s8拼接而成,但是jvm并沒有對(duì)其進(jìn)行優(yōu)化,所以s1!=s9
最后,上面這段代碼輸出:
- s1 == s2 true
- s1 == s3 true
- s1 == s4 false
- s1 == s6 true
- s1 == s9 false
- s4 == s5 false
JVM中的常量池也是享元模式的經(jīng)典實(shí)現(xiàn)之一。
關(guān)于String延伸內(nèi)容:
美團(tuán)面試題:String s = new String("111")會(huì)創(chuàng)建幾個(gè)對(duì)象?
Long中的享元模式
Long中和Integer中類似,也是最-128到127的數(shù)進(jìn)行了緩存,請(qǐng)看Long中的valueOf()方法源碼部分:
- public static Long valueOf(long l) {
- final int offset = 128;
- if (l >= -128 && l <= 127) { // will cache
- return LongCache.cache[(int)l + offset];
- }
- return new Long(l);
- }
這個(gè)就沒必要進(jìn)行演示了,和Integer一樣,都是使用了緩存,也就是享元模式。
在Apache Commons Pool中的享元模式
對(duì)象池化的基本思路是:將用過的對(duì)象保存起來,等下一次需要這種對(duì)象的時(shí)候,再拿出來重復(fù)使用,從而在一定程度上減少頻繁創(chuàng)建對(duì)象造成的消耗。用于充當(dāng)保存對(duì)象的“容器”的對(duì)象,被稱為對(duì)象池(Object Pool,簡稱Pool)。
Apache Pool實(shí)現(xiàn)了對(duì)象池的功能,定義了對(duì)象的生成、銷毀、激活、鈍化等操作及其狀態(tài)轉(zhuǎn)換,并提供幾個(gè)默認(rèn)的對(duì)象池實(shí)現(xiàn),
有如下幾個(gè)重要的角色:
- Pooled Object(池化對(duì)象):用于封裝對(duì)象(例如,線程、數(shù)據(jù)庫連接和TCP連接),將其包裹成可被對(duì)象池管理的對(duì)象。
- Pooled Object Factory(池化對(duì)象工廠):定義了操作Pooled Object實(shí)例生命周期的一些方法,Pooled Object Factory必須實(shí)現(xiàn)線程安全。
- Object Pool(對(duì)象池):Object Pool負(fù)責(zé)管理Pooled Object,例如,借出對(duì)象、返回對(duì)象、校驗(yàn)對(duì)象、有多少激活對(duì)象和有多少空閑對(duì)象。
在ObjectPool類的子類org.apache.commons.pool2.impl.GenericObjectPool種有個(gè)屬性:
- private final Map
, PooledObject > allObjects;
這個(gè)Map就是用來緩存對(duì)象的,所以這里也是享元模式的實(shí)現(xiàn)。
享元模式的擴(kuò)展
享元模式中的狀態(tài)
享元模式的定義提出了兩個(gè)要求:細(xì)粒度和共享對(duì)象。
因?yàn)橐蠹?xì)粒度,所以不可避免地會(huì)使對(duì)象數(shù)量多且性質(zhì)相近,此時(shí)我們就將這些對(duì)象的信息分為兩個(gè)部分:內(nèi)部狀態(tài)和外部狀態(tài)。
內(nèi)部狀態(tài)指對(duì)象共享出來的信息,存儲(chǔ)在享元對(duì)象內(nèi)部,并且不會(huì)隨環(huán)境的改變而改變;
外部狀態(tài)指對(duì)象得以依賴的一個(gè)標(biāo)記,隨環(huán)境的改變而改變,不可共享。
比如:連接池中的連接對(duì)象,保存在連接對(duì)象中的用戶名、密碼、連接URL等信息,在創(chuàng)建對(duì)象的時(shí)候就設(shè)置好了,不會(huì)隨環(huán)境的改變而改變,這些為內(nèi)部狀態(tài)。而當(dāng)每個(gè)連接要被回收利用時(shí),我們需要將它標(biāo)記為可用狀態(tài),這些為外部狀態(tài)。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 減少對(duì)象的創(chuàng)建,降低內(nèi)存中對(duì)象的數(shù)量,降低系統(tǒng)的內(nèi)存,提高效率。
- 減少內(nèi)存之外的其他資源占用。
缺點(diǎn)
- 關(guān)注內(nèi)、外部狀態(tài),關(guān)注線程安全問題。
- 使系統(tǒng)、程序的邏輯復(fù)雜化。
總結(jié)
享元模式,單從概念來講估計(jì)很多人不是很理解,但是從Integer、String已經(jīng)生活中的場(chǎng)景結(jié)合起來理解,就能輕松理解享元模式,享元模式的實(shí)現(xiàn)基本上都伴隨著一個(gè)集合用來存這些對(duì)象。
一句話總結(jié):
優(yōu)化資源配置,減少資源浪費(fèi)
參考:Tom的設(shè)計(jì)模式課程
好了,今天的分享就到此結(jié)束,希望大家能明白什么是享元模式,享元模式的思想我們?cè)陂_發(fā)中是否能借鑒,面試的時(shí)候就不要再說你不會(huì)設(shè)計(jì)模式了。
本文轉(zhuǎn)載自微信公眾號(hào)「Java后端技術(shù)全棧」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系Java后端技術(shù)全棧公眾號(hào)。
當(dāng)前名稱:泡圖書館,我想到了享元模式
鏈接URL:http://m.5511xx.com/article/djcdgjs.html


咨詢
建站咨詢
