23種設(shè)計(jì)模式(21)java享元模式
在閻宏博士的《JAVA與模式》一書中開頭是這樣描述享元(Flyweight)模式的:
Flyweight在拳擊比賽中指最輕量級(jí),即“蠅量級(jí)”或“雨量級(jí)”,這里選擇使用“享元模式”的意譯,是因?yàn)檫@樣更能反映模式的用意。享元模式是對(duì)象的結(jié)構(gòu)模式。享元模式以共享的方式高效地支持大量的細(xì)粒度對(duì)象。
Java中的String類型
在JAVA語言中,String類型就是使用了享元模式。String對(duì)象是final類型,對(duì)象一旦創(chuàng)建就不可改變。在JAVA中字符串常量都是存在常量池中的,JAVA會(huì)確保一個(gè)字符串常量在常量池中只有一個(gè)拷貝。String a="abc",其中"abc"就是一個(gè)字符串常量。
public class Test { public static void main(String[] args) { String a = "abc"; String b = "abc"; System.out.println(a==b); } }
上面的例子中結(jié)果為:true ,這就說明a和b兩個(gè)引用都指向了常量池中的同一個(gè)字符串常量"abc"。這樣的設(shè)計(jì)避免了在創(chuàng)建N多相同對(duì)象時(shí)所產(chǎn)生的不必要的大量的資源消耗。
享元模式的結(jié)構(gòu)
享元模式采用一個(gè)共享來避免大量擁有相同內(nèi)容對(duì)象的開銷。這種開銷最常見、最直觀的就是內(nèi)存的損耗。享元對(duì)象能做到共享的關(guān)鍵是區(qū)分內(nèi)蘊(yùn)狀態(tài)(Internal State)和外蘊(yùn)狀態(tài)(External State) 。
一個(gè)內(nèi)蘊(yùn)狀態(tài)是存儲(chǔ)在享元對(duì)象內(nèi)部的,并且是不會(huì)隨環(huán)境的改變而有所不同。因此,一個(gè)享元可以具有內(nèi)蘊(yùn)狀態(tài)并可以共享。
一個(gè)外蘊(yùn)狀態(tài)是隨環(huán)境的改變而改變的、不可以共享的。享元對(duì)象的外蘊(yùn)狀態(tài)必須由客戶端保存,并在享元對(duì)象被創(chuàng)建之后,在需要使用的時(shí)候再傳入到享元對(duì)象內(nèi)部。外蘊(yùn)狀態(tài)不可以影響享元對(duì)象的內(nèi)蘊(yùn)狀態(tài),它們是相互獨(dú)立的。
享元模式可以分成單純享元模式和復(fù)合享元模式兩種形式。
單純享元模式
在單純的享元模式中,所有的享元對(duì)象都是可以共享的。
單純享元模式所涉及到的角色如下:
抽象享元(Flyweight)角色:給出一個(gè)抽象接口,以規(guī)定出所有具體享元角色需要實(shí)現(xiàn)的方法。
具體享元(ConcreteFlyweight)角色:實(shí)現(xiàn)抽象享元角色所規(guī)定出的接口。如果有內(nèi)蘊(yùn)狀態(tài)的話,必須負(fù)責(zé)為內(nèi)蘊(yùn)狀態(tài)提供存儲(chǔ)空間。
享元工廠(FlyweightFactory)角色:本角色負(fù)責(zé)創(chuàng)建和管理享元角色。本角色必須保證享元對(duì)象可以被系統(tǒng)適當(dāng)?shù)毓蚕?。?dāng)一個(gè)客戶端對(duì)象調(diào)用一個(gè)享元對(duì)象的時(shí)候,享元工廠角色會(huì)檢查系統(tǒng)中是否已經(jīng)有一個(gè)符合要求的享元對(duì)象。如果已經(jīng)有了,享元工廠角色就應(yīng)當(dāng)提供這個(gè)已有的享元對(duì)象;如果系統(tǒng)中沒有一個(gè)適當(dāng)?shù)南碓獙?duì)象的話,享元工廠角色就應(yīng)當(dāng)創(chuàng)建一個(gè)合適的享元對(duì)象。
源代碼
抽象享元角色類
public interface Flyweight { //一個(gè)示意性方法,參數(shù)state是外蘊(yùn)狀態(tài) public void operation(String state); }
具體享元角色類ConcreteFlyweight有一個(gè)內(nèi)蘊(yùn)狀態(tài),在本例中一個(gè)Character類型的intrinsicState屬性代表,它的值應(yīng)當(dāng)在享元對(duì)象被創(chuàng)建時(shí)賦予。所有的內(nèi)蘊(yùn)狀態(tài)在對(duì)象創(chuàng)建之后,就不會(huì)再改變了。
如果一個(gè)享元對(duì)象有外蘊(yùn)狀態(tài)的話,所有的外部狀態(tài)都必須存儲(chǔ)在客戶端,在使用享元對(duì)象時(shí),再由客戶端傳入享元對(duì)象。這里只有一個(gè)外蘊(yùn)狀態(tài),operation()方法的參數(shù)state就是由外部傳入的外蘊(yùn)狀態(tài)。
public class ConcreteFlyweight implements Flyweight { private Character intrinsicState = null; /** * 構(gòu)造函數(shù),內(nèi)蘊(yùn)狀態(tài)作為參數(shù)傳入 * @param state */ public ConcreteFlyweight(Character state){ this.intrinsicState = state; } /** * 外蘊(yùn)狀態(tài)作為參數(shù)傳入方法中,改變方法的行為, * 但是并不改變對(duì)象的內(nèi)蘊(yùn)狀態(tài)。 */ @Override public void operation(String state) { // TODO Auto-generated method stub System.out.println("Intrinsic State = " + this.intrinsicState); System.out.println("Extrinsic State = " + state); } }
享元工廠角色類,必須指出的是,客戶端不可以直接將具體享元類實(shí)例化,而必須通過一個(gè)工廠對(duì)象,利用一個(gè)factory()方法得到享元對(duì)象。一般而言,享元工廠對(duì)象在整個(gè)系統(tǒng)中只有一個(gè),因此也可以使用單例模式。
當(dāng)客戶端需要單純享元對(duì)象的時(shí)候,需要調(diào)用享元工廠的factory()方法,并傳入所需的單純享元對(duì)象的內(nèi)蘊(yùn)狀態(tài),由工廠方法產(chǎn)生所需要的享元對(duì)象。
public class FlyweightFactory { private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>(); public Flyweight factory(Character state){ //先從緩存中查找對(duì)象 Flyweight fly = files.get(state); if(fly == null){ //如果對(duì)象不存在則創(chuàng)建一個(gè)新的Flyweight對(duì)象 fly = new ConcreteFlyweight(state); //把這個(gè)新的Flyweight對(duì)象添加到緩存中 files.put(state, fly); } return fly; } }
客戶端類
public class Client { public static void main(String[] args) { // TODO Auto-generated method stub FlyweightFactory factory = new FlyweightFactory(); Flyweight fly = factory.factory(new Character('a')); fly.operation("First Call"); fly = factory.factory(new Character('b')); fly.operation("Second Call"); fly = factory.factory(new Character('a')); fly.operation("Third Call"); } }
雖然客戶端申請(qǐng)了三個(gè)享元對(duì)象,但是實(shí)際創(chuàng)建的享元對(duì)象只有兩個(gè),這就是共享的含義。運(yùn)行結(jié)果如下:
復(fù)合享元模式
在單純享元模式中,所有的享元對(duì)象都是單純享元對(duì)象,也就是說都是可以直接共享的。還有一種較為復(fù)雜的情況,將一些單純享元使用合成模式加以復(fù)合,形成復(fù)合享元對(duì)象。這樣的復(fù)合享元對(duì)象本身不能共享,但是它們可以分解成單純享元對(duì)象,而后者則可以共享。
復(fù)合享元角色所涉及到的角色如下:
抽象享元(Flyweight)角色:給出一個(gè)抽象接口,以規(guī)定出所有具體享元角色需要實(shí)現(xiàn)的方法。
具體享元(ConcreteFlyweight)角色:實(shí)現(xiàn)抽象享元角色所規(guī)定出的接口。如果有內(nèi)蘊(yùn)狀態(tài)的話,必須負(fù)責(zé)為內(nèi)蘊(yùn)狀態(tài)提供存儲(chǔ)空間。
復(fù)合享元(ConcreteCompositeFlyweight)角色:復(fù)合享元角色所代表的對(duì)象是不可以共享的,但是一個(gè)復(fù)合享元對(duì)象可以分解成為多個(gè)本身是單純享元對(duì)象的組合。復(fù)合享元角色又稱作不可共享的享元對(duì)象。
享元工廠(FlyweightFactory)角色:本角 色負(fù)責(zé)創(chuàng)建和管理享元角色。本角色必須保證享元對(duì)象可以被系統(tǒng)適當(dāng)?shù)毓蚕怼.?dāng)一個(gè)客戶端對(duì)象調(diào)用一個(gè)享元對(duì)象的時(shí)候,享元工廠角色會(huì)檢查系統(tǒng)中是否已經(jīng)有 一個(gè)符合要求的享元對(duì)象。如果已經(jīng)有了,享元工廠角色就應(yīng)當(dāng)提供這個(gè)已有的享元對(duì)象;如果系統(tǒng)中沒有一個(gè)適當(dāng)?shù)南碓獙?duì)象的話,享元工廠角色就應(yīng)當(dāng)創(chuàng)建一個(gè) 合適的享元對(duì)象。
源代碼
抽象享元角色類
public interface Flyweight { //一個(gè)示意性方法,參數(shù)state是外蘊(yùn)狀態(tài) public void operation(String state); }
具體享元角色類
public class ConcreteFlyweight implements Flyweight { private Character intrinsicState = null; /** * 構(gòu)造函數(shù),內(nèi)蘊(yùn)狀態(tài)作為參數(shù)傳入 * @param state */ public ConcreteFlyweight(Character state){ this.intrinsicState = state; } /** * 外蘊(yùn)狀態(tài)作為參數(shù)傳入方法中,改變方法的行為, * 但是并不改變對(duì)象的內(nèi)蘊(yùn)狀態(tài)。 */ @Override public void operation(String state) { // TODO Auto-generated method stub System.out.println("Intrinsic State = " + this.intrinsicState); System.out.println("Extrinsic State = " + state); } }
復(fù)合享元對(duì)象是由單純享元對(duì)象通過復(fù)合而成的,因此它提供了add()這樣的聚集管理方法。由于一個(gè)復(fù)合享元對(duì)象具有不同的聚集元素,這些聚集元素在復(fù)合享元對(duì)象被創(chuàng)建之后加入,這本身就意味著復(fù)合享元對(duì)象的狀態(tài)是會(huì)改變的,因此復(fù)合享元對(duì)象是不能共享的。
復(fù)合享元角色實(shí)現(xiàn)了抽象享元角色所規(guī)定的接口,也就是operation()方法,這個(gè)方法有一個(gè)參數(shù),代表復(fù)合享元對(duì)象的外蘊(yùn)狀態(tài)。一個(gè)復(fù)合享元對(duì)象的所有單純享元對(duì)象元素的外蘊(yùn)狀態(tài)都是與復(fù)合享元對(duì)象的外蘊(yùn)狀態(tài)相等的;而一個(gè)復(fù)合享元對(duì)象所含有的單純享元對(duì)象的內(nèi)蘊(yùn)狀態(tài)一般是不相等的,不然就沒有使用價(jià)值了。
public class ConcreteCompositeFlyweight implements Flyweight { private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>(); /** * 增加一個(gè)新的單純享元對(duì)象到聚集中 */ public void add(Character key , Flyweight fly){ files.put(key,fly); } /** * 外蘊(yùn)狀態(tài)作為參數(shù)傳入到方法中 */ @Override public void operation(String state) { Flyweight fly = null; for(Object o : files.keySet()){ fly = files.get(o); fly.operation(state); } } }
享元工廠角色提供兩種不同的方法,一種用于提供單純享元對(duì)象,另一種用于提供復(fù)合享元對(duì)象。
public class FlyweightFactory { private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>(); /** * 復(fù)合享元工廠方法 */ public Flyweight factory(List<Character> compositeState){ ConcreteCompositeFlyweight compositeFly = new ConcreteCompositeFlyweight(); for(Character state : compositeState){ compositeFly.add(state,this.factory(state)); } return compositeFly; } /** * 單純享元工廠方法 */ public Flyweight factory(Character state){ //先從緩存中查找對(duì)象 Flyweight fly = files.get(state); if(fly == null){ //如果對(duì)象不存在則創(chuàng)建一個(gè)新的Flyweight對(duì)象 fly = new ConcreteFlyweight(state); //把這個(gè)新的Flyweight對(duì)象添加到緩存中 files.put(state, fly); } return fly; } }
客戶端角色
public class Client { public static void main(String[] args) { List<Character> compositeState = new ArrayList<Character>(); compositeState.add('a'); compositeState.add('b'); compositeState.add('c'); compositeState.add('a'); compositeState.add('b'); FlyweightFactory flyFactory = new FlyweightFactory(); Flyweight compositeFly1 = flyFactory.factory(compositeState); Flyweight compositeFly2 = flyFactory.factory(compositeState); compositeFly1.operation("Composite Call"); System.out.println("---------------------------------"); System.out.println("復(fù)合享元模式是否可以共享對(duì)象:" + (compositeFly1 == compositeFly2)); Character state = 'a'; Flyweight fly1 = flyFactory.factory(state); Flyweight fly2 = flyFactory.factory(state); System.out.println("單純享元模式是否可以共享對(duì)象:" + (fly1 == fly2)); } }
運(yùn)行結(jié)果如下:
從運(yùn)行結(jié)果可以看出,一個(gè)復(fù)合享元對(duì)象的所有單純享元對(duì)象元素的外蘊(yùn)狀態(tài)都是與復(fù)合享元對(duì)象的外蘊(yùn)狀態(tài)相等的。即外運(yùn)狀態(tài)都等于Composite Call。
從運(yùn)行結(jié)果可以看出,一個(gè)復(fù)合享元對(duì)象所含有的單純享元對(duì)象的內(nèi)蘊(yùn)狀態(tài)一般是不相等的。即內(nèi)蘊(yùn)狀態(tài)分別為b、c、a。
從運(yùn)行結(jié)果可以看出,復(fù)合享元對(duì)象是不能共享的。即使用相同的對(duì)象compositeState通過工廠分別兩次創(chuàng)建出的對(duì)象不是同一個(gè)對(duì)象。
從運(yùn)行結(jié)果可以看出,單純享元對(duì)象是可以共享的。即使用相同的對(duì)象state通過工廠分別兩次創(chuàng)建出的對(duì)象是同一個(gè)對(duì)象。
享元模式的優(yōu)缺點(diǎn)
享元模式的優(yōu)點(diǎn)在于它大幅度地降低內(nèi)存中對(duì)象的數(shù)量。但是,它做到這一點(diǎn)所付出的代價(jià)也是很高的:
享元模式使得系統(tǒng)更加復(fù)雜。為了使對(duì)象可以共享,需要將一些狀態(tài)外部化,這使得程序的邏輯復(fù)雜化。
享元模式將享元對(duì)象的狀態(tài)外部化,而讀取外部狀態(tài)使得運(yùn)行時(shí)間稍微變長。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java中StringBuilder字符串類型的操作方法及API整理
Java中的StringBuffer類繼承于AbstractStringBuilder,用來創(chuàng)建非線程安全的字符串類型對(duì)象,下面即是對(duì)Java中StringBuilder字符串類型的操作方法及API整理2016-05-05SpringBoot詳細(xì)講解多個(gè)配置文件的配置流程
SpringBoot項(xiàng)目是一個(gè)標(biāo)準(zhǔn)的Maven項(xiàng)目,它的配置文件需要放在src/main/resources/下,其文件名必須為application,其存在兩種文件形式,分別是properties和yaml(或者yml)文件2022-06-06SpringBoot-RestTemplate實(shí)現(xiàn)調(diào)用第三方API的方式
RestTemplate?是由?Spring?提供的一個(gè)?HTTP?請(qǐng)求工具,它提供了常見的REST請(qǐng)求方案的模版,例如?GET?請(qǐng)求、POST?請(qǐng)求、PUT?請(qǐng)求、DELETE?請(qǐng)求以及一些通用的請(qǐng)求執(zhí)行方法?exchange?以及?execute,下面看下SpringBoot?RestTemplate調(diào)用第三方API的方式2022-12-12Java利用反射自動(dòng)封裝成實(shí)體對(duì)象的方法
這篇文章主要介紹了Java利用反射自動(dòng)封裝成實(shí)體對(duì)象的方法,可實(shí)現(xiàn)自動(dòng)封裝成bean對(duì)象功能,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-01-01java synchronized實(shí)現(xiàn)可見性過程解析
這篇文章主要介紹了java synchronized實(shí)現(xiàn)可見性過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09springboot使用之多個(gè)filter的執(zhí)行順序以及配置方式
這篇文章主要介紹了springboot使用之多個(gè)filter的執(zhí)行順序以及配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08Spring通過<import>標(biāo)簽導(dǎo)入外部配置文件
之前文章里我們講到Spring加載Xml配置文件的細(xì)節(jié),那么加載完了我們肯定要解析這個(gè)配置文件中定義的元素。這篇我們首先來分析下Spring是如何通過標(biāo)簽導(dǎo)入外部配置文件的。2021-06-06