C語言中文網 目錄
首頁 > 設計模式 閱讀:1,192

享元模式(詳解版)

< 上一頁外觀模式 組合模式下一頁 >

在面向對象程序設計過程中,有時會面臨要創建大量相同或相似對象實例的問題。創建那么多的對象將會耗費很多的系統資源,它是系統性能提高的一個瓶頸。例如,圍棋和五子棋中的黑白棋子,圖像中的坐標點或顏色,局域網中的路由器、交換機和集線器,教室里的桌子和凳子等。這些對象有很多相似的地方,如果能把它們相同的部分提取出來共享,則能節省大量的系統資源,這就是享元模式的產生背景。

享元模式的定義與特點

享元(Flyweight)模式的定義:運用共享技術來有効地支持大量細粒度對象的復用。它通過共享已經存在的又橡來大幅度減少需要創建的對象數量、避免大量相似類的開銷,從而提高系統資源的利用率。

享元模式的主要優點是:相同對象只要保存一份,這降低了系統中對象的數量,從而降低了系統中細粒度對象給內存帶來的壓力。

其主要缺點是:
  1. 為了使對象可以共享,需要將一些不能共享的狀態外部化,這將增加程序的復雜性。
  2. 讀取享元模式的外部狀態會使得運行時間稍微變長。

享元模式的結構與實現

享元模式中存在以下兩種狀態:
  1. 內部狀態,即不會隨著環境的改變而改變的可共享部分;
  2. 外部狀態,指隨環境改變而改變的不可以共享的部分。享元模式的實現要領就是區分應用中的這兩種狀態,并將外部狀態外部化。下面來分析其基本結構和實現方法。

1. 模式的結構

享元模式的主要角色有如下。
  1. 抽象享元角色(Flyweight):是所有的具體享元類的基類,為具體享元規范需要實現的公共接口,非享元的外部狀態以參數的形式通過方法傳入。
  2. 具體享元(Concrete Flyweight)角色:實現抽象享元角色中所規定的接口。
  3. 非享元(Unsharable Flyweight)角色:是不可以共享的外部狀態,它以參數的形式注入具體享元的相關方法中。
  4. 享元工廠(Flyweight Factory)角色:負責創建和管理享元角色。當客戶對象請求一個享元對象時,享元工廠檢査系統中是否存在符合要求的享元對象,如果存在則提供給客戶;如果不存在的話,則創建一個新的享元對象。

圖 1 是享元模式的結構圖。圖中的 UnsharedConcreteFlyweight 是與淳元角色,里面包含了非共享的外部狀態信息 info;而 Flyweight 是抽象享元角色,里面包含了享元方法 operation(UnsharedConcreteFlyweight state),非享元的外部狀態以參數的形式通過該方法傳入;ConcreteFlyweight 是具體享元角色,包含了關鍵字 key,它實現了抽象享元接口;FlyweightFactory 是享元工廠角色,它逝關鍵字 key 來管理具體享元;客戶角色通過享元工廠獲取具體享元,并訪問具體享元的相關方法。

享元模式的結構圖
圖1 享元模式的結構圖

2. 模式的實現

享元模式的實現代碼如下:
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次調用a。"));       
        f02.operation(new UnsharedConcreteFlyweight("第2次調用a。"));       
        f03.operation(new UnsharedConcreteFlyweight("第3次調用a。"));       
        f11.operation(new UnsharedConcreteFlyweight("第1次調用b。"));       
        f12.operation(new UnsharedConcreteFlyweight("第2次調用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+"被創建!");
    }
    public void operation(UnsharedConcreteFlyweight outState)
    {
        System.out.print("具體享元"+key+"被調用,");
        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+"已經存在,被成功獲??!");
        }
        else
        {
            flyweight=new ConcreteFlyweight(key);
            flyweights.put(key, flyweight);
        }
        return flyweight;
    }
}

程序運行結果如下:
具體享元a被創建!
具體享元a已經存在,被成功獲??!
具體享元a已經存在,被成功獲??!
具體享元b被創建!
具體享元b已經存在,被成功獲??!
具體享元a被調用,非享元信息是:第1次調用a。
具體享元a被調用,非享元信息是:第2次調用a。
具體享元a被調用,非享元信息是:第3次調用a。
具體享元b被調用,非享元信息是:第1次調用b。
具體享元b被調用,非享元信息是:第2次調用b。

享元模式的應用實例

【例1】享元模式在五子棋游戲中的應用。

分析:五子棋同圍棋一樣,包含多個“黑”或“白”顏色的棋子,所以用享元模式比較好。

本實例中的棋子(ChessPieces)類是抽象享元角色,它包含了一個落子的 DownPieces(Graphics g,Point pt) 方法;白子(WhitePieces)和黑子(BlackPieces)類是具體享元角色,它實現了落子方法;Point 是非享元角色,它指定了落子的位置;WeiqiFactory 是享元工廠角色,它通過 ArrayList 來管理棋子,并且提供了獲取白子或者黑子的 getChessPieces(String type) 方法;客戶類(Chessboard)利用 Graphics 組件在框架窗體中繪制一個棋盤,并實現 mouseClicked(MouseEvent e) 事件處理方法,該方法根據用戶的選擇從享元工廠中獲取白子或者黑子并落在棋盤上。圖 2 所示是其結構圖。

五子棋游戲的結構圖
圖2 五子棋游戲的結構圖

程序代碼如下:
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("享元模式在五子棋游戲中的應用");
        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;
        }
    }
}

程序運行結果如圖 3 所示。
五子棋游戲的運行結果
圖3 五子棋游戲的運行結果

享元模式的應用場景

前面分析了享元模式的結構與特點,下面分析它適用的應用場景。享元模式是通過減少內存中對象的數量來節省內存空間的,所以以下幾種情形適合采用享元模式。
  1. 系統中存在大量相同或相似的對象,這些對象耗費大量的內存資源。
  2. 大部分的對象可以按照內部狀態進行分組,且可將不同部分外部化,這樣每一個組只需保存一個內部狀態。
  3. 由于享元模式需要額外維護一個保存享元的數據結構,所以應當在有足夠多的享元實例時才值得使用享元模式。

享元模式的擴展

在前面介紹的享元模式中,其結構圖通常包含可以共享的部分和不可以共享的部分。在實際使用過程中,有時候會稍加改變,即存在兩種特殊的享元模式:單純享元模式和復合享元模式,下面分別對它們進行簡單介紹。

(1) 單純享元模式,這種享元模式中的所有的具體享元類都是可以共享的,不存在非共享的具體享元類,其結構圖如圖 4 所示。

單純享元模式的結構圖
圖4 單純享元模式的結構圖

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

復合享元模式的結構圖
圖5 復合享元模式的結構圖
< 上一頁外觀模式 組合模式下一頁 >

精美而實用的網站,提供C語言、C++、STL、Linux、Shell、Java、Go語言等教程,以及socket、GCC、vi、Swing、設計模式、JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo