簡(jiǎn)介
享元模式(Flyweight Pattern)是一種軟件設(shè)計(jì)模式,用于減少內(nèi)存使用和提高性能。它通過(guò)共享細(xì)粒度對(duì)象來(lái)減少創(chuàng)建和銷(xiāo)毀對(duì)象時(shí)所需的內(nèi)存。享元模式適用于大量相似對(duì)象的場(chǎng)景,這些對(duì)象可以共享相同的狀態(tài)和行為。
享元模式的核心思想是將對(duì)象分為內(nèi)部狀態(tài)和外部狀態(tài)。內(nèi)部狀態(tài)是對(duì)象自身的狀態(tài),通常是不可變的,可以共享;而外部狀態(tài)則是與對(duì)象相關(guān)聯(lián)的環(huán)境信息,通常是變化的,由客戶端傳入享元對(duì)象內(nèi)部。通過(guò)共享內(nèi)部狀態(tài),可以顯著減少系統(tǒng)中的對(duì)象數(shù)量,從而降低內(nèi)存消耗。
結(jié)構(gòu)
享元模式包含以下幾個(gè)角色:
- 抽象享元類(lèi)(Flyweight):通常是一個(gè)接口或抽象類(lèi),聲明一些可以向外界提供享元對(duì)象內(nèi)部狀態(tài)的方法,同時(shí)也可以通過(guò)這些方法設(shè)置內(nèi)部狀態(tài)。
- 具體享元類(lèi)(ConcreteFlyweight):實(shí)現(xiàn)了抽象享元類(lèi),為內(nèi)部狀態(tài)提供了存儲(chǔ)空間。
- 非共享具體享元類(lèi)(UnsharedConcreteFlyweight):并不是所有的抽象享元類(lèi)的子類(lèi)都需要被共享,不能被共享的子類(lèi)可以設(shè)計(jì)為非共享具體享元類(lèi),當(dāng)需要使用非共享具體享元類(lèi)時(shí)可以直接實(shí)例化創(chuàng)建。
- 享元工廠類(lèi)(FlyweightFactory):享元工廠類(lèi)用于創(chuàng)建并管理享元對(duì)象,將各個(gè)類(lèi)型的具體享元對(duì)象存儲(chǔ)在一個(gè)享元池中,享元池一般設(shè)計(jì)為鍵值對(duì)集合結(jié)構(gòu)。
標(biāo)準(zhǔn)的享元模式結(jié)構(gòu)包含可以共享的具體享元類(lèi)和不可以共享的非共享具體享元類(lèi)。根據(jù)具體情況,可以分為單純享元模式和復(fù)合享元模式。單純享元模式中所有具體享元類(lèi)都是可以共享的,而復(fù)合享元模式則將一些單純享元對(duì)象使用組合模式加以組合,形成復(fù)合享元對(duì)象。
案例實(shí)現(xiàn)
例】俄羅斯方塊
下面的圖片是眾所周知的俄羅斯方塊中的一個(gè)個(gè)方塊,如果在俄羅斯方塊這個(gè)游戲中,每個(gè)不同的方塊都是一個(gè)實(shí)例對(duì)象,這些對(duì)象就要占用很多的內(nèi)存空間,下面利用享元模式進(jìn)行實(shí)現(xiàn)。
先來(lái)看類(lèi)圖:
代碼如下:
俄羅斯方塊有不同的形狀,我們可以對(duì)這些形狀向上抽取出AbstractBox,用來(lái)定義共性的屬性和行為。
public abstract class AbstractBox {
public abstract String getShape();
public void display(String color) {
System.out.println("方塊形狀:" + this.getShape() + " 顏色:" + color);
}
}
接下來(lái)就是定義不同的形狀了,IBox類(lèi)、LBox類(lèi)、OBox類(lèi)等。
public class IBox extends AbstractBox {
@Override
public String getShape() {
return "I";
}
}
public class LBox extends AbstractBox {
@Override
public String getShape() {
return "L";
}
}
public class OBox extends AbstractBox {
@Override
public String getShape() {
return "O";
}
}
提供了一個(gè)工廠類(lèi)(BoxFactory),用來(lái)管理享元對(duì)象(也就是AbstractBox子類(lèi)對(duì)象),該工廠類(lèi)對(duì)象只需要一個(gè),所以可以使用單例模式。并給工廠類(lèi)提供一個(gè)獲取形狀的方法。
public class BoxFactory {
private static HashMap<String, AbstractBox> map;
private BoxFactory() {
map = new HashMap<String, AbstractBox>();
AbstractBox iBox = new IBox();
AbstractBox lBox = new LBox();
AbstractBox oBox = new OBox();
map.put("I", iBox);
map.put("L", lBox);
map.put("O", oBox);
}
public static final BoxFactory getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final BoxFactory INSTANCE = new BoxFactory();
}
public AbstractBox getBox(String key) {
return map.get(key);
}
}
優(yōu)缺點(diǎn)和使用場(chǎng)景
1,優(yōu)點(diǎn):
- 降低內(nèi)存消耗:通過(guò)共享內(nèi)部狀態(tài),可以顯著減少系統(tǒng)中的對(duì)象數(shù)量,從而降低內(nèi)存消耗。
- 提高性能:由于減少了對(duì)象的創(chuàng)建和銷(xiāo)毀次數(shù),可以提高系統(tǒng)的性能。
- 提高復(fù)用性:通過(guò)將對(duì)象的狀態(tài)分離出來(lái),可以使多個(gè)對(duì)象共享相同的狀態(tài),提高了代碼的復(fù)用性。
2,缺點(diǎn):
- 實(shí)現(xiàn)復(fù)雜度較高:需要分離出內(nèi)部狀態(tài)和外部狀態(tài),使得程序邏輯復(fù)雜化。
- 需要分離出共享狀態(tài)和非共享狀態(tài):為了實(shí)現(xiàn)對(duì)象的共享,需要將對(duì)象的共享狀態(tài)和非共享狀態(tài)進(jìn)行分離,這可能會(huì)增加代碼的復(fù)雜度。
3,使用場(chǎng)景:
- 一個(gè)系統(tǒng)有大量相同或者相似的對(duì)象,造成內(nèi)存的大量耗費(fèi)。
- 對(duì)象的大部分狀態(tài)都可以外部化,可以將這些外部狀態(tài)傳入對(duì)象中。
- 在使用享元模式時(shí)需要維護(hù)一個(gè)存儲(chǔ)享元對(duì)象的享元池,而這需要耗費(fèi)一定的系統(tǒng)資源,因此,應(yīng)當(dāng)在需要多次重復(fù)使用享元對(duì)象時(shí)才值得使用享元模式。
源碼中的應(yīng)用
Spring中
在Spring框架中,享元模式的應(yīng)用主要體現(xiàn)在以下幾個(gè)方面:
- Bean的作用域管理:Spring框架中的Bean默認(rèn)是單例的,這意味著在整個(gè)Spring容器中,每個(gè)Bean的ID都對(duì)應(yīng)著一個(gè)唯一的實(shí)例對(duì)象。這種單例模式實(shí)際上是享元模式的一種應(yīng)用。通過(guò)共享相同的Bean實(shí)例,減少了對(duì)象的創(chuàng)建和銷(xiāo)毀,從而節(jié)約了系統(tǒng)資源,提高了應(yīng)用程序的性能。
- 事件處理機(jī)制:在Spring的事件處理機(jī)制中,享元模式被用來(lái)管理事件監(jiān)聽(tīng)器對(duì)象的創(chuàng)建和銷(xiāo)毀。通過(guò)共享已經(jīng)存在的監(jiān)聽(tīng)器對(duì)象,避免了大量相似對(duì)象的創(chuàng)建,從而節(jié)省了系統(tǒng)資源,提高了事件處理的效率。
- BeanFactory管理:在Spring的BeanFactory中,也使用了享元模式來(lái)管理Bean對(duì)象的創(chuàng)建和銷(xiāo)毀。BeanFactory負(fù)責(zé)實(shí)例化、配置和管理Bean,通過(guò)共享Bean的實(shí)例,實(shí)現(xiàn)了對(duì)象的復(fù)用,進(jìn)一步提高了系統(tǒng)資源的利用率。
- 緩存機(jī)制:在Spring Security等模塊中,權(quán)限信息等數(shù)據(jù)經(jīng)常被緩存以提高性能。這些緩存的實(shí)例在需要時(shí)被創(chuàng)建,并在多個(gè)上下文中共享,這也是享元模式的一種應(yīng)用。通過(guò)共享相同的權(quán)限對(duì)象實(shí)例,減少了對(duì)象的創(chuàng)建和內(nèi)存占用,提高了系統(tǒng)的響應(yīng)速度。
JDK中
享元模式在JDK中的應(yīng)用主要體現(xiàn)在一些系統(tǒng)庫(kù)和組件的設(shè)計(jì)中,旨在通過(guò)共享對(duì)象來(lái)減少內(nèi)存使用和提高性能。下面是一些具體的例子:
-
字符串常量池:在JDK中,字符串常量是通過(guò)享元模式來(lái)管理的。當(dāng)我們使用雙引號(hào)創(chuàng)建字符串字面量時(shí),JVM會(huì)首先檢查字符串常量池中是否存在相同的字符串。如果存在,則返回該字符串的引用;如果不存在,則在常量池中創(chuàng)建一個(gè)新的字符串對(duì)象,并返回其引用。這種機(jī)制減少了相似字符串對(duì)象的創(chuàng)建,節(jié)省了內(nèi)存空間。
-
包裝類(lèi)緩存:Java中的包裝類(lèi)(如Integer、Boolean等)對(duì)于小范圍的數(shù)值,如Integer在-128到127之間,使用了享元模式。這些范圍內(nèi)的對(duì)象會(huì)被緩存起來(lái),當(dāng)需要?jiǎng)?chuàng)建這些范圍內(nèi)的對(duì)象時(shí),會(huì)直接從緩存中獲取,而不是重新創(chuàng)建。這種機(jī)制減少了包裝類(lèi)對(duì)象的創(chuàng)建,提高了性能。
例:java.lang.Integer
https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/Integer.java
public class Demo { public static void main(String[] args) { Integer i1 = 127; Integer i2 = 127; System.out.println("i1和i2對(duì)象是否是同一個(gè)對(duì)象?" + (i1 == i2)); Integer i3 = 128; Integer i4 = 128; System.out.println("i3和i4對(duì)象是否是同一個(gè)對(duì)象?" + (i3 == i4)); } }
運(yùn)行上面代碼,結(jié)果如下:
為什么第一個(gè)輸出語(yǔ)句輸出的是true,第二個(gè)輸出語(yǔ)句輸出的是false?通過(guò)反編譯軟件進(jìn)行反編譯,代碼如下:
public class Demo {
public static void main(String[] args) {
Integer i1 = Integer.valueOf((int)127);
Integer i2 Integer.valueOf((int)127);
System.out.println((String)new StringBuilder().append((String)"i1\u548ci2\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i1 == i2)).toString());
Integer i3 = Integer.valueOf((int)128);
Integer i4 = Integer.valueOf((int)128);
System.out.println((String)new StringBuilder().append((String)"i3\u548ci4\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i3 == i4)).toString());
}
}
上面代碼可以看到,直接給Integer類(lèi)型的變量賦值基本數(shù)據(jù)類(lèi)型數(shù)據(jù)的操作底層使用的是 valueOf()
,所以只需要看該方法即可
public final class Integer extends Number implements Comparable<Integer> {
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
}
可以看到 Integer
默認(rèn)先創(chuàng)建并緩存 -128 ~ 127
之間數(shù)的 Integer
對(duì)象,當(dāng)調(diào)用 valueOf
時(shí)如果參數(shù)在 -128 ~ 127
之間則計(jì)算下標(biāo)并從緩存中返回,否則創(chuàng)建一個(gè)新的 Integer
對(duì)象。
-
集合框架中的對(duì)象復(fù)用:在JDK的集合框架中,有些實(shí)現(xiàn)也采用了享元模式的思想。例如,在HashMap等集合類(lèi)中,為了優(yōu)化性能,可能會(huì)復(fù)用某些內(nèi)部對(duì)象,而不是每次都創(chuàng)建新的對(duì)象。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-851717.html
-
線程池:JDK中的線程池(如ExecutorService)也是享元模式的一種應(yīng)用。線程池通過(guò)復(fù)用已有的線程,減少了線程的創(chuàng)建和銷(xiāo)毀開(kāi)銷(xiāo),提高了系統(tǒng)的并發(fā)處理能力。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-851717.html
到了這里,關(guān)于Java 設(shè)計(jì)模式系列:享元模式的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!