结构模式->享元模式

         减少内存浪费  Sunny软件公司开发人员通过对围棋软件进行分析,发现在围棋棋盘中包含大量的黑子和白子,它们的形状、大小都一模一样,只是出现的位置不同而已。如果将每一个棋子都作为一个独立的对象存储在内存中,将导致该围棋软件在运行时所需内存空间较大,如何降低运行代价、提高系统性能是Sunny公司开发人员需要解决的一个问题为了解决这个问题,Sunny公司开发人员决定使用享元模式来设计该围棋软件的棋子对象,那么享元模式是如何实现节约内存进而提高系统性能的呢?

        享元模式通过共享技术实现相同或相似对象的重用,在逻辑上每一个出现的字符都有一个对象与之对应,然而在物理上它们却共享同一个享元对象这个对象可以出现在一个字符串的不同地方,相同的字符对象都指向同一个实例,在享元模式中,存储这些共享实例对象的地方称为享元池(Flyweight Pool)我们可以针对每一个不同的字符创建一个享元对象,将其放在享元池中,需要时再从享元池取出。如图14-2所示:

14-2 字符享元对象示意图

      享元模式以共享的方式高效地支持大量细粒度对象的重用,享元对象能做到共享的关键是区分了内部状态(Intrinsic State)外部状态(Extrinsic State)。下面将对享元的内部状态和外部状态进行简单的介绍:

      (1)  内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享如字符的内容,不会随外部环境的变化而变化,无论在任何环境下字符“a”始终是“a”,都不会变成“b”。

      (2)  外部状态是随环境改变而改变的、不可以共享的状态享元对象的外部状态通常由客户端保存,并在享元对象被创建之后,需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态之间是相互独立的。如字符的颜色,可以在不同的地方有不同的颜色,例如有的“a”是红色的,有的“a”是绿色的,字符的大小也是如此,有的“a”是五号字,有的“a”是四号字。而且字符的颜色和大小是两个独立的外部状态,它们可以独立变化,相互之间没有影响,客户端可以在使用时将外部状态注入享元对象中。

      正因为区分了内部状态和外部状态,我们可以将具有相同内部状态的对象存储在享元池中,享元池中的对象是可以实现共享的,需要的时候就将对象从享元池中取出,实现对象的复用。通过向取出的对象注入不同的外部状态,可以得到一系列相似的对象,而这些对象在内存中实际上只存储一份。

      享元模式定义如下:

享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。

[cpp] view plain copy print?
//棋子颜色  
enum PieceColor {BLACK, WHITE};  
//棋子位置  
struct PiecePos  
{  
    int x;  
    int y;  
    PiecePos(int a, int b): x(a), y(b) {}  
};  
//棋子定义  
class Piece  
{  
protected:  
    PieceColor m_color; //颜色  
    PiecePos m_pos;     //位置  
public:  
    Piece(PieceColor color, PiecePos pos): m_color(color), m_pos(pos) {}  
    ~Piece() {}  
    virtual void Draw() {}  
};  
class BlackPiece: public Piece  
{  
public:  
    BlackPiece(PieceColor color, PiecePos pos): Piece(color, pos) {}  
    ~BlackPiece() {}  
    void Draw() { cout<<"绘制一颗黑棋"<<endl;}  
};  
class WhitePiece: public Piece  
{  
public:  
    WhitePiece(PieceColor color, PiecePos pos): Piece(color, pos) {}  
    ~WhitePiece() {}  
    void Draw() { cout<<"绘制一颗白棋"<<endl;}  
};  
        棋盘的定义:
[cpp] view plain copy print?
class PieceBoard  
{  
private:  
    vector<Piece*> m_vecPiece; //棋盘上已有的棋子  
    string m_blackName; //黑方名称  
    string m_whiteName; //白方名称  
public:  
    PieceBoard(string black, string white): m_blackName(black), m_whiteName(white){}  
    ~PieceBoard() { Clear(); }  
    void SetPiece(PieceColor color, PiecePos pos) //一步棋,在棋盘上放一颗棋子  
    {  
        Piece * piece = NULL;  
        if(color == BLACK) //黑方下的  
        {     
            piece = new BlackPiece(color, pos); //获取一颗黑棋  
            cout<<m_blackName<<"在位置("<<pos.x<<','<<pos.y<<")";  
            piece->Draw(); //在棋盘上绘制出棋子  
        }  
        else  
        {     
            piece = new WhitePiece(color, pos);  
            cout<<m_whiteName<<"在位置("<<pos.x<<','<<pos.y<<")";  
            piece->Draw();  
        }  
        m_vecPiece.push_back(piece);  //加入容器中  
    }  
    void Clear() //释放内存  
    {  
        int size = m_vecPiece.size();  
        for(int i = 0; i < size; i++)  
            delete m_vecPiece[i];  
    }  
};  
        客户的使用方式如下:
[cpp] view plain copy print?
int main()  
{  
    PieceBoard pieceBoard("A","B");  
    pieceBoard.SetPiece(BLACK, PiecePos(4, 4));  
    pieceBoard.SetPiece(WHITE, PiecePos(4, 16));  
    pieceBoard.SetPiece(BLACK, PiecePos(16, 4));  
    pieceBoard.SetPiece(WHITE, PiecePos(16, 16));  
}  
       可以发现,棋盘的容器中存放了已下的棋子,而每个棋子包含棋子的所有属性。一盘棋往往需要含上百颗棋子,采用上面这种实现,占用的空间太大了。如何改进呢?用享元模式。其定义为:运用共享技术有效地支持大量细粒度的对象。
        在围棋中,棋子就是大量细粒度的对象。其属性有内在的,比如颜色、形状等,也有外在的,比如在棋盘上的位置。内在的属性是可以共享的,区分在于外在属性。因此,可以这样设计,只需定义两个棋子的对象,一颗黑棋和一颗白棋,这两个对象含棋子的内在属性;棋子的外在属性,即在棋盘上的位置可以提取出来,存放在单独的容器中。相比之前的方案,现在容器中仅仅存放了位置属性,而原来则是棋子对象。显然,现在的方案大大减少了对于空间的需求。
       关注PieceBoard 的容器,之前是vector<Piece*> m_vecPiece,现在是vector<PiecePos> m_vecPos。这里是关键。
       棋子的新定义,只包含内在属性:
[cpp] view plain copy print?
//棋子颜色  
enum PieceColor {BLACK, WHITE};  
//棋子位置  
struct PiecePos  
{  
    int x;  
    int y;  
    PiecePos(int a, int b): x(a), y(b) {}  
};  
//棋子定义  
class Piece  
{  
protected:  
    PieceColor m_color; //颜色  
public:  
    Piece(PieceColor color): m_color(color) {}  
    ~Piece() {}  
    virtual void Draw() {}  
};  
class BlackPiece: public Piece  
{  
public:  
    BlackPiece(PieceColor color): Piece(color) {}  
    ~BlackPiece() {}  
    void Draw() { cout<<"绘制一颗黑棋\n"; }  
};  
class WhitePiece: public Piece  
{  
public:  
    WhitePiece(PieceColor color): Piece(color) {}  
    ~WhitePiece() {}  
    void Draw() { cout<<"绘制一颗白棋\n";}  
};  
        相应棋盘的定义为:
[cpp] view plain copy print?
class PieceBoard  
{  
private:  
    vector<PiecePos> m_vecPos; //存放棋子的位置  
    Piece *m_blackPiece;       //黑棋棋子   
    Piece *m_whitePiece;       //白棋棋子  
    string m_blackName;  
    string m_whiteName;  
public:  
    PieceBoard(string black, string white): m_blackName(black), m_whiteName(white)  
    {  
        m_blackPiece = NULL;  
        m_whitePiece = NULL;  
    }  
    ~PieceBoard() { delete m_blackPiece; delete m_whitePiece;}  
    void SetPiece(PieceColor color, PiecePos pos)  
    {  
        if(color == BLACK)  
        {  
            if(m_blackPiece == NULL)  //只有一颗黑棋  
                m_blackPiece = new BlackPiece(color);     
            cout<<m_blackName<<"在位置("<<pos.x<<','<<pos.y<<")";  
            m_blackPiece->Draw();  
        }  
        else  
        {  
            if(m_whitePiece == NULL)  
                m_whitePiece = new WhitePiece(color);  
            cout<<m_whiteName<<"在位置("<<pos.x<<','<<pos.y<<")";  
            m_whitePiece->Draw();  
        }  
        m_vecPos.push_back(pos);  
    }  
};  
       客户的使用方式一样,这里不重复给出,现在给出享元模式的UML图,以围棋为例。棋盘中含两个共享的对象,黑棋子和白棋子,所有棋子的外在属性都存放在单独的容器中。


posted @ 2017-05-05 13:47  sowhat1412  阅读(94)  评论(0编辑  收藏  举报