一文徹底搞明白享元模式
本篇講解Java設(shè)計模式中的享元模式,分為定義、模式應(yīng)用前案例、結(jié)構(gòu)、模式應(yīng)用后案例、適用場景、模式可能存在的困惑和本質(zhì)探討7個部分。
定義
享元模式指的是運用共享技術(shù)有效地支持大量細粒度的對象。
在新的分類方式中,享元模式被劃分至類的屬性相關(guān)類別中,其應(yīng)對類的不同對象可以共享內(nèi)部狀態(tài)(可共享的屬性)的要求。

模式應(yīng)用前案例
在享元模式中,我們來看一個文本編輯器的案例。對于文本編輯器來說,可以包括添加文本、顯示文本、清空文本等操作。對于其中的文本,都有自身的文本樣式。
下面,先來看一下未使用享元模式的案例,代碼實現(xiàn)如下。
public class Text {//文本類
private final String content;
private final TextStyle style;
public Text(String content, TextStyle style) {
this.content = content;
this.style = style;
}
public void display() {
System.out.println("Text: " + this.content + ", Style: " + this.style);
}
}
public class TextStyle {//文本樣式類
private final String font;
private final int size;
private final String color;
public TextStyle(String font, int size, String color) {
this.font = font;
this.size = size;
this.color = color;
}
@Override
public String toString() {
return "Font: "+ this.font +", Size: "+ this.size +", Color: "+ this.color;
}
}
public class TextEditor {// 文本編輯器類
List<Text> textList;
public TextEditor(){
this.textList =new ArrayList<>();
}
// 添加文本到編輯器中
public void addText(Text text){
this.textList.add(text);
}
// 顯示所有文本內(nèi)容和樣式信息
public void displayAllTexts(){
for(Text text : this.textList){
text.display();
}
}
// 清空所有文本內(nèi)容
public void clearAllTexts(){
this.textList.clear();
System.out.println("All texts cleared.");
}
}調(diào)用方代碼如下。
public class Client {//調(diào)用方代碼
public static void main(String[] args) {
// 創(chuàng)建文本編輯器對象
TextEditor textEditor = new TextEditor();
// 創(chuàng)建兩個相同樣式(Arial 12 Black)的文字片段并添加到編輯器中
TextStyle textStyle1 = new TextStyle("Arial", 12, "Black");
Text text1 = new Text("Hello World", textStyle1);
TextStyle textStyle2 = new TextStyle("Arial", 12, "Black");
Text text2 = new Text("Welcome to the world of programming!", textStyle2);
textEditor.addText(text1);
textEditor.addText(text2);
// 顯示所有文本內(nèi)容和樣式信息
System.out.println("Displaying all texts:");
textEditor.displayAllTexts();
// 清空所有文本內(nèi)容
System.out.println("\nClearing all texts:");
textEditor.clearAllTexts();
}
}在上述代碼中,文本樣式類是文本類中的屬性。對于每一個文本,即便文本樣式相同,代碼中也會創(chuàng)建新的文本樣式類。
直觀感覺上,就會覺得此處是否可以復(fù)用呢?對于此種場景,就適合使用享元模式來進行處理。
結(jié)構(gòu)

享元模式的示例代碼實現(xiàn)如下。
public interface Flyweight {
void Operation(int extrinsicstate);
}
public class ConereteFlyweight implements Flyweight {
@Override
public void Operation(int extrinsicstate) {
System.out.println("共享的具體Flyweight:" +extrinsicstate);
}
}
public class UnsharedConereteFlyweight implements Flyweight{
@Override
public void Operation(int extrinsicstate) {
System.out.println("不共享的具體Flyweight:" +extrinsicstate);
}
}
public class FlyweightFactory {
private static final Map<String, Flyweight> flyweights = new HashMap<>();
public FlyweightFactory() {
flyweights.put("A", new ConereteFlyweight());
flyweights.put("B", new ConereteFlyweight());
flyweights.put("C", new ConereteFlyweight());
}
public Flyweight getFlyweight(String key) {
return flyweights.get(key);
}
}
public class Client {
public static void main(String[] args) {
int extrinsicstate = 1;
FlyweightFactory factory = new FlyweightFactory();
Flyweight fa = factory.getFlyweight("A");
fa.Operation(extrinsicstate);
Flyweight fb = factory.getFlyweight("B");
fb.Operation(extrinsicstate);
Flyweight fc = factory.getFlyweight("C");
fc.Operation(extrinsicstate);
UnsharedConereteFlyweight uf = new UnsharedConereteFlyweight();
uf.Operation(extrinsicstate);
}
}可以發(fā)現(xiàn),在享元模式下,先將類的屬性分成兩種類別,一類是多個對象可以共享的狀態(tài),另一類是多個對象私有的狀態(tài)。
然后,通過接口或繼承實現(xiàn)的一個類家族實現(xiàn)上述兩種類別,即圖中的Flyweight及具體實現(xiàn)類。
此外,提供一個FlyweightFactory支撐類,這個類的作用就是負責(zé)創(chuàng)建一些包含共享狀態(tài)的對象,然后緩存起來。
當(dāng)調(diào)用方需要用到時,就可以通過FlyweightFactory來獲取到共享的狀態(tài)。同時,那些不能共享的狀態(tài)(或?qū)傩裕部梢酝ㄟ^UnsharedConcreteFlyweight類來創(chuàng)建。
模式應(yīng)用后案例
上述文本編輯器的案例,在使用享元模式后的代碼實現(xiàn)如下。
public class Text {//文本類
private final String content;
private final TextStyle style;
public Text(String content, TextStyle style) {
this.content = content;
this.style = style;
}
public void display() {
System.out.println("Text: " + this.content + ", Style: " + this.style);
}
}
public class TextStyle {//文本樣式類
private final String font;
private final int size;
private final String color;
public TextStyle(String font, int size, String color) {
this.font = font;
this.size = size;
this.color = color;
}
@Override
public String toString() {
return "Font: "+ this.font +", Size: "+ this.size +", Color: "+ this.color;
}
}文本Text類中的TextStyle屬性可以共享,content屬性不能共享。因此,可以創(chuàng)建一個TextFactory類來共享TextStyle。
public class TextFactory {//享元工廠類
private static final HashMap<String, TextStyle> stylesMap = new HashMap<>();
public static TextStyle getTextStyle(String font,int size,String color){
stylesMap.putIfAbsent(font+size+color,new TextStyle(font,size,color));
return stylesMap.get(font+size+color);
}
}享元工廠類有了之后,文本編輯器類代碼如下。
public class TextEditor {//文本編輯器類
List<Text> textList;
public TextEditor(){
this.textList =new ArrayList<>();
}
// 添加文本到編輯器中
public void addText(String content, String font, int size, String color){
TextStyle sharedStyle = TextFactory.getTextStyle(font, size, color);
this.textList.add(new Text(content, sharedStyle));
}
// 顯示所有文本內(nèi)容和樣式信息
public void displayAllTexts(){
for(Text text : this.textList){
text.display();
}
}
// 清空所有文本內(nèi)容
public void clearAllTexts(){
this.textList.clear();
System.out.println("All texts cleared.");
}
}調(diào)用方代碼如下。
public class Client {//調(diào)用方代碼
public static void main(String[] args) {
// 創(chuàng)建文本編輯器對象
TextEditor editor = new TextEditor();
// 使用編輯器添加相同樣式(Arial 12 Black)的文字片段
editor.addText("Hello World", "Arial", 12, "Black");
editor.addText("Welcome to the world of programming!", "Arial", 12,"Black");
// 顯示所有文本內(nèi)容和樣式信息
System.out.println("Displaying all texts:");
editor.displayAllTexts();
}
}上述代碼中,在需要用到TextStyle的地方,通過享元工廠類獲取,而不是直接通過new方式創(chuàng)建,這樣所有文本類都可以共享一個TextStyle對象,這就是享元模式發(fā)揮作用的地方。
適用場景
當(dāng)程序中需要創(chuàng)建大量對象,并且這些對象共享一部分內(nèi)部狀態(tài),為了節(jié)省內(nèi)存空間,就可以考慮使用享元模式。
比如,在Java語言中,Integer、String等類,由于不同對象可能共享相同的字面量,因此其內(nèi)部都應(yīng)用了享元模式。
模式可能存在的困惑
困惑1:享元模式中,經(jīng)常提到的內(nèi)部狀態(tài)與外部狀態(tài),是什么含義?
一個類中可以包含多個屬性,其中可以被多個對象共享的狀態(tài)或?qū)傩猿蔀椤皟?nèi)部狀態(tài)”,而每個對象獨有的屬性或狀態(tài)則成為“外部狀態(tài)”。
困惑2:結(jié)構(gòu)圖中的Client與FlyweightFactory類有交互,但是案例中的TextEditor類中有使用TextFactory類,Client類并沒有使用?
在應(yīng)用設(shè)計模式時,有時候不能按照設(shè)計模式標(biāo)準(zhǔn)結(jié)構(gòu)來生搬硬套。關(guān)鍵在于理解設(shè)計模式的核心思想之后能夠靈活運用。
案例中的TextEditor其實相當(dāng)于結(jié)構(gòu)中的Client。Text類相當(dāng)于Flyweight類,而TextStyle類Text類中的共享狀態(tài)。案例的結(jié)構(gòu)與結(jié)構(gòu)中的結(jié)構(gòu)并不完全相同,但是對于享元模式中的共享思想運用是一致的。
困惑3:享元模式相當(dāng)于是為了對象的復(fù)用,以節(jié)省存儲空間。在日常編程中,像對象池、線程池、連接池等技術(shù)也是為了對象的復(fù)用,那么區(qū)別在哪里呢?
主要區(qū)別在于時間觀上的不同。對于對象池、線程池、連接池等技術(shù)來說,在同一時刻,一個共享的對象只能用于一個調(diào)用方。只有該對象使用完畢重新放在池子之后,其他調(diào)用方才能使用。
而對于享元模式來說,同一時刻,可以被許多調(diào)用方同時共享復(fù)用,不需要在時間上錯開使用。
本質(zhì)
享元模式的本質(zhì)是對類中屬性更細粒度的控制。在享元模式中,是通過狀態(tài)是否共享來分類的。
通過分類,就可以采取不同的操作。在享元模式中,通過狀態(tài)是否共享這種分類方式,就可以用來節(jié)省存儲空間,并且節(jié)省空間的同時其實也可以節(jié)省對象創(chuàng)建的時間。
這里值得提醒一下,時間和空間并不是在任何場景下都是一組矛盾關(guān)系,如空間增加可以減少時間,空間變少時間就會加長等。在享元模式中,是對象復(fù)雜度的降低導(dǎo)致了空間和時間都在某種程度上都有所減少。
當(dāng)然,對于類中屬性也可能有其他分類方式,如本質(zhì)屬性和偶然屬性的劃分等。享元模式僅僅是處理這些分類方式中的一種特例。






























