享元模式(Flyweight)
在面向?qū)ο蟪绦蛟O(shè)計(jì)過(guò)程中,有時(shí)會(huì)面臨要?jiǎng)?chuàng)建大量相同或相似對(duì)象實(shí)例的問(wèn)題。創(chuàng)建那么多的對(duì)象將會(huì)耗費(fèi)很多的系統(tǒng)資源,它是系統(tǒng)性能提高的一個(gè)瓶頸。例如,圍棋和五子棋中的黑白棋子,圖像中的坐標(biāo)點(diǎn)或顏色,局域網(wǎng)中的路由器、交換機(jī)和集線器,教室里的桌子和凳子等。這些對(duì)象有很多相似的地方,如果能把它們相同的部分提取出來(lái)共享,則能節(jié)省大量的系統(tǒng)資源,這就是享元模式的產(chǎn)生背景。
享元模式的定義與特點(diǎn)
-
享元(Flyweight)模式的定義:
運(yùn)用共享技術(shù)來(lái)有効地支持大量細(xì)粒度對(duì)象的復(fù)用。它通過(guò)共享已經(jīng)存在的又橡來(lái)大幅度減少需要?jiǎng)?chuàng)建的對(duì)象數(shù)量、避免大量相似類的開(kāi)銷,從而提高系統(tǒng)資源的利用率。 -
享元模式的優(yōu)點(diǎn):
相同對(duì)象只要保存一份,這降低了系統(tǒng)中對(duì)象的數(shù)量,從而降低了系統(tǒng)中細(xì)粒度對(duì)象給內(nèi)存帶來(lái)的壓力。 -
享元模式的缺點(diǎn):
1.為了使對(duì)象可以共享,需要將一些不能共享的狀態(tài)外部化,這將增加程序的復(fù)雜性。
2.讀取享元模式的外部狀態(tài)會(huì)使得運(yùn)行時(shí)間稍微變長(zhǎng)。
享元模式的結(jié)構(gòu)與實(shí)現(xiàn)
享元模式中存在以下兩種狀態(tài):
- 內(nèi)部狀態(tài),即不會(huì)隨著環(huán)境的改變而改變的可共享部分;
- 外部狀態(tài),指隨環(huán)境改變而改變的不可以共享的部分。享元模式的實(shí)現(xiàn)要領(lǐng)就是區(qū)分應(yīng)用中的這兩種狀態(tài),并將外部狀態(tài)外部化。下面來(lái)分析其基本結(jié)構(gòu)和實(shí)現(xiàn)方法。
1. 模式的結(jié)構(gòu)
享元模式的主要角色有如下。
- 抽象享元角色(Flyweight): 是所有的具體享元類的基類,為具體享元規(guī)范需要實(shí)現(xiàn)的公共接口,非享元的外部狀態(tài)以參數(shù)的形式通過(guò)方法傳入。
- 具體享元(Concrete Flyweight)角色: 實(shí)現(xiàn)抽象享元角色中所規(guī)定的接口。
- 非享元(Unsharable Flyweight)角色: 是不可以共享的外部狀態(tài),它以參數(shù)的形式注入具體享元的相關(guān)方法中。
- 享元工廠(Flyweight Factory)角色: 負(fù)責(zé)創(chuàng)建和管理享元角色。當(dāng)客戶對(duì)象請(qǐng)求一個(gè)享元對(duì)象時(shí),享元工廠檢査系統(tǒng)中是否存在符合要求的享元對(duì)象,如果存在則提供給客戶;如果不存在的話,則創(chuàng)建一個(gè)新的享元對(duì)象。
圖 1 是享元模式的結(jié)構(gòu)圖。圖中的 UnsharedConcreteFlyweight 是與淳元角色,里面包含了非共享的外部狀態(tài)信息 info;而 Flyweight 是抽象享元角色,里面包含了享元方法 operation(UnsharedConcreteFlyweight state),非享元的外部狀態(tài)以參數(shù)的形式通過(guò)該方法傳入;ConcreteFlyweight 是具體享元角色,包含了關(guān)鍵字 key,它實(shí)現(xiàn)了抽象享元接口;FlyweightFactory 是享元工廠角色,它逝關(guān)鍵字 key 來(lái)管理具體享元;客戶角色通過(guò)享元工廠獲取具體享元,并訪問(wèn)具體享元的相關(guān)方法。

2. 模式的實(shí)現(xiàn)
享元模式的實(shí)現(xiàn)代碼如下:
package flyweight;
import java.util.HashMap;
public class FlyweightPattern
{
public static void main(String[] args)
{
FlyweightFactory factory=new FlyweightFactory();
Flyweight f01=factory.getFlyweight("a");
Flyweight f02=factory.getFlyweight("a");
Flyweight f03=factory.getFlyweight("a");
Flyweight f11=factory.getFlyweight("b");
Flyweight f12=factory.getFlyweight("b");
f01.operation(new UnsharedConcreteFlyweight("第1次調(diào)用a。"));
f02.operation(new UnsharedConcreteFlyweight("第2次調(diào)用a。"));
f03.operation(new UnsharedConcreteFlyweight("第3次調(diào)用a。"));
f11.operation(new UnsharedConcreteFlyweight("第1次調(diào)用b。"));
f12.operation(new UnsharedConcreteFlyweight("第2次調(diào)用b。"));
}
}
//非享元角色
class UnsharedConcreteFlyweight
{
private String info;
UnsharedConcreteFlyweight(String info)
{
this.info=info;
}
public String getInfo()
{
return info;
}
public void setInfo(String info)
{
this.info=info;
}
}
//抽象享元角色
interface Flyweight
{
public void operation(UnsharedConcreteFlyweight state);
}
//具體享元角色
class ConcreteFlyweight implements Flyweight
{
private String key;
ConcreteFlyweight(String key)
{
this.key=key;
System.out.println("具體享元"+key+"被創(chuàng)建!");
}
public void operation(UnsharedConcreteFlyweight outState)
{
System.out.print("具體享元"+key+"被調(diào)用,");
System.out.println("非享元信息是:"+outState.getInfo());
}
}
//享元工廠角色
class FlyweightFactory
{
private HashMap<String, Flyweight> flyweights=new HashMap<String, Flyweight>();
public Flyweight getFlyweight(String key)
{
Flyweight flyweight=(Flyweight)flyweights.get(key);
if(flyweight!=null)
{
System.out.println("具體享元"+key+"已經(jīng)存在,被成功獲??!");
}
else
{
flyweight=new ConcreteFlyweight(key);
flyweights.put(key, flyweight);
}
return flyweight;
}
}
程序運(yùn)行結(jié)果如下:
具體享元a被創(chuàng)建!
具體享元a已經(jīng)存在,被成功獲??!
具體享元a已經(jīng)存在,被成功獲?。?具體享元b被創(chuàng)建!
具體享元b已經(jīng)存在,被成功獲?。?具體享元a被調(diào)用,非享元信息是:第1次調(diào)用a。
具體享元a被調(diào)用,非享元信息是:第2次調(diào)用a。
具體享元a被調(diào)用,非享元信息是:第3次調(diào)用a。
具體享元b被調(diào)用,非享元信息是:第1次調(diào)用b。
具體享元b被調(diào)用,非享元信息是:第2次調(diào)用b。
享元模式的實(shí)例
【例】享元模式在五子棋游戲中的應(yīng)用。
分析:五子棋同圍棋一樣,包含多個(gè)“黑”或“白”顏色的棋子,所以用享元模式比較好。
本實(shí)例中的棋子(ChessPieces)類是抽象享元角色,它包含了一個(gè)落子的 DownPieces(Graphics g,Point pt) 方法;白子(WhitePieces)和黑子(BlackPieces)類是具體享元角色,它實(shí)現(xiàn)了落子方法;Point 是非享元角色,它指定了落子的位置;WeiqiFactory 是享元工廠角色,它通過(guò) ArrayList 來(lái)管理棋子,并且提供了獲取白子或者黑子的 getChessPieces(String type) 方法;客戶類(Chessboard)利用 Graphics 組件在框架窗體中繪制一個(gè)棋盤,并實(shí)現(xiàn) mouseClicked(MouseEvent e) 事件處理方法,該方法根據(jù)用戶的選擇從享元工廠中獲取白子或者黑子并落在棋盤上。圖 2 所示是其結(jié)構(gòu)圖。

程序代碼如下:
package flyweight;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import javax.swing.*;
public class WzqGame
{
public static void main(String[] args)
{
new Chessboard();
}
}
//棋盤
class Chessboard extends MouseAdapter
{
WeiqiFactory wf;
JFrame f;
Graphics g;
JRadioButton wz;
JRadioButton bz;
private final int x=50;
private final int y=50;
private final int w=40; //小方格寬度和高度
private final int rw=400; //棋盤寬度和高度
Chessboard()
{
wf=new WeiqiFactory();
f=new JFrame("享元模式在五子棋游戲中的應(yīng)用");
f.setBounds(100,100,500,550);
f.setVisible(true);
f.setResizable(false);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel SouthJP=new JPanel();
f.add("South",SouthJP);
wz=new JRadioButton("白子");
bz=new JRadioButton("黑子",true);
ButtonGroup group=new ButtonGroup();
group.add(wz);
group.add(bz);
SouthJP.add(wz);
SouthJP.add(bz);
JPanel CenterJP=new JPanel();
CenterJP.setLayout(null);
CenterJP.setSize(500, 500);
CenterJP.addMouseListener(this);
f.add("Center",CenterJP);
try
{
Thread.sleep(500);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
g=CenterJP.getGraphics();
g.setColor(Color.BLUE);
g.drawRect(x, y, rw, rw);
for(int i=1;i<10;i++)
{
//繪制第i條豎直線
g.drawLine(x+(i*w),y,x+(i*w),y+rw);
//繪制第i條水平線
g.drawLine(x,y+(i*w),x+rw,y+(i*w));
}
}
public void mouseClicked(MouseEvent e)
{
Point pt=new Point(e.getX()-15,e.getY()-15);
if(wz.isSelected())
{
ChessPieces c1=wf.getChessPieces("w");
c1.DownPieces(g,pt);
}
else if(bz.isSelected())
{
ChessPieces c2=wf.getChessPieces("b");
c2.DownPieces(g,pt);
}
}
}
//抽象享元角色:棋子
interface ChessPieces
{
public void DownPieces(Graphics g,Point pt); //下子
}
//具體享元角色:白子
class WhitePieces implements ChessPieces
{
public void DownPieces(Graphics g,Point pt)
{
g.setColor(Color.WHITE);
g.fillOval(pt.x,pt.y,30,30);
}
}
//具體享元角色:黑子
class BlackPieces implements ChessPieces
{
public void DownPieces(Graphics g,Point pt)
{
g.setColor(Color.BLACK);
g.fillOval(pt.x,pt.y,30,30);
}
}
//享元工廠角色
class WeiqiFactory
{
private ArrayList<ChessPieces> qz;
public WeiqiFactory()
{
qz=new ArrayList<ChessPieces>();
ChessPieces w=new WhitePieces();
qz.add(w);
ChessPieces b=new BlackPieces();
qz.add(b);
}
public ChessPieces getChessPieces(String type)
{
if(type.equalsIgnoreCase("w"))
{
return (ChessPieces)qz.get(0);
}
else if(type.equalsIgnoreCase("b"))
{
return (ChessPieces)qz.get(1);
}
else
{
return null;
}
}
}
程序運(yùn)行結(jié)果如圖 3 所示。

享元模式的應(yīng)用場(chǎng)景
- 系統(tǒng)中存在大量相同或相似的對(duì)象,這些對(duì)象耗費(fèi)大量的內(nèi)存資源。
- 大部分的對(duì)象可以按照內(nèi)部狀態(tài)進(jìn)行分組,且可將不同部分外部化,這樣每一個(gè)組只需保存一個(gè)內(nèi)部狀態(tài)。
- 由于享元模式需要額外維護(hù)一個(gè)保存享元的數(shù)據(jù)結(jié)構(gòu),所以應(yīng)當(dāng)在有足夠多的享元實(shí)例時(shí)才值得使用享元模式。
享元模式的擴(kuò)展
在前面介紹的享元模式中,其結(jié)構(gòu)圖通常包含可以共享的部分和不可以共享的部分。在實(shí)際使用過(guò)程中,有時(shí)候會(huì)稍加改變,即存在兩種特殊的享元模式:?jiǎn)渭兿碓J胶蛷?fù)合享元模式,下面分別對(duì)它們進(jìn)行簡(jiǎn)單介紹。
(1) 單純享元模式,這種享元模式中的所有的具體享元類都是可以共享的,不存在非共享的具體享元類,其結(jié)構(gòu)圖如圖 4 所示。

(2) 復(fù)合享元模式,這種享元模式中的有些享元對(duì)象是由一些單純享元對(duì)象組合而成的,它們就是復(fù)合享元對(duì)象。雖然復(fù)合享元對(duì)象本身不能共享,但它們可以分解成單純享元對(duì)象再被共享,其結(jié)構(gòu)圖如圖 5 所示。
