• C++设计模式——享元模式


    什么是享元模式?

    在GOF的《设计模式:可复用面向对象软件的基础》一书中对享元模式是这样说的:运用共享技术有效地支持大量细粒度的对象。

    就如上面说的棋子,如果每个棋子都new一个对象,就会存在大量细粒度的棋子对象,这对服务器的内存空间是一种考验,也是一种浪费。我们都知道,比如我在2013号房间和别人下五子棋,2014号房间也有人在下五子棋,并不会因为我在2013号房间,而别人在2014号房间,而导致我们的棋子是不一样的。这就是说,2013号房间和2014号房间的棋子都是一样的,所有的五子棋房间的棋子都是一样的。唯一的不同是每个棋子在不同的房间的不同棋盘的不同位置上。所以,对于棋子来说,我们不用放一个棋子就new一个棋子对象,只需要在需要的时候,去请求获得对应的棋子对象,如果没有,就new一个棋子对象;如果有了,就直接返回棋子对象。这里以五子棋为例子,进行分析,当玩家在棋盘上放入第一个白色棋子时,此时由于没有白色棋子,所以就new一个白色棋子;当另一个玩家放入第一个黑色棋子时,此时由于没有黑色棋子,所以就需要new一个黑色棋子;当玩家再次放入一个白色棋子时,就去查询是否有已经存在的白色棋子对象,由于第一次已经new了一个白色棋子对象,所以,现在不会再次new一个白色棋子对象,而是返回以前new的白色棋子对象;对于黑色棋子,亦是同理;获得了棋子对象,我们只需要设置棋子的不同棋盘位置即可。

     

    UML类图

    Flyweight:描述一个接口,通过这个接口flyweight可以接受并作用于外部状态;

    ConcreteFlyweight:实现Flyweight接口,并为定义了一些内部状态,ConcreteFlyweight对象必须是可共享的;同时,它所存储的状态必须是内部的;即,它必须独立于ConcreteFlyweight对象的场景;

    UnsharedConcreteFlyweight:并非所有的Flyweight子类都需要被共享。Flyweight接口使共享成为可能,但它并不强制共享。

    FlyweightFactory:创建并管理flyweight对象。它需要确保合理地共享flyweight;当用户请求一个flyweight时,FlyweightFactory对象提供一个已创建的实例,如果请求的实例不存在的情况下,就新创建一个实例;

    Client:维持一个对flyweight的引用;同时,它需要计算或存储flyweight的外部状态。

     

    实现要点

    根据我们的经验,当要将一个对象进行共享时,就需要考虑到对象的状态问题了;不同的客户端获得共享的对象之后,可能会修改共享对象的某些状态;大家都修改了共享对象的状态,那么就会出现对象状态的紊乱。对于享元模式,在实现时一定要考虑到共享对象的状态问题。那么享元模式是如何实现的呢?

    在享元模式中,有两个非常重要的概念:内部状态和外部状态。

    内部状态存储于flyweight中,它包含了独立于flyweight场景的信息,这些信息使得flyweight可以被共享。而外部状态取决于flyweight场景,并根据场景而变化,因此不可共享。用户对象负责在必要的时候将外部状态传递给flyweight。

    flyweight执行时所需的状态必定是内部的或外部的。内部状态存储于ConcreteFlyweight对象之中;而外部对象则由Client对象存储或计算。当用户调用flyweight对象的操作时,将该状态传递给它。同时,用户不应该直接对ConcreteFlyweight类进行实例化,而只能从FlyweightFactory对象得到ConcreteFlyweight对象,这可以保证对它们适当地进行共享;由于共享一个实例,所以在创建这个实例时,就可以考虑使用单例模式来进行实现。

    享元模式的工厂类维护了一个实例列表,这个列表中保存了所有的共享实例;当用户从享元模式的工厂类请求共享对象时,首先查询这个实例表,如果不存在对应实例,则创建一个;如果存在,则直接返回对应的实例。

     

    代码实现

      1 #include <iostream>
      2 #include <map>
      3 #include <vector>
      4 using namespace std;
      5 
      6 typedef struct pointTag
      7 {
      8     int x;
      9     int y;
     10 
     11     pointTag(){}
     12     pointTag(int a, int b)
     13     {
     14         x = a;
     15     y = b;
     16     }
     17 
     18     bool operator <(const pointTag& other) const
     19     {
     20         if (x < other.x)
     21         {
     22             return true;
     23         }
     24         else if (x == other.x)
     25         {
     26             return y < other.y;
     27         }
     28 
     29         return false;
     30     }
     31 }POINT;
     32 
     33 typedef enum PieceColorTag
     34 {
     35     BLACK,
     36     WHITE
     37 }PIECECOLOR;
     38 
     39 class CPiece
     40 {
     41 public:
     42     CPiece(PIECECOLOR color) : m_color(color){}
     43     PIECECOLOR GetColor() { return m_color; }
     44 
     45     // Set the external state
     46     void SetPoint(POINT point) { m_point = point; }
     47     POINT GetPoint() { return m_point; }
     48 
     49 protected:
     50     // Internal state
     51     PIECECOLOR m_color;
     52 
     53     // external state
     54     POINT m_point;
     55 };
     56 
     57 class CGomoku : public CPiece
     58 {
     59 public:
     60     CGomoku(PIECECOLOR color) : CPiece(color){}
     61 };
     62 
     63 class CPieceFactory
     64 {
     65 public:
     66     CPiece *GetPiece(PIECECOLOR color)
     67     {
     68         CPiece *pPiece = NULL;
     69     if (m_vecPiece.empty())
     70     {
     71         pPiece = new CGomoku(color);
     72         m_vecPiece.push_back(pPiece);
     73     }
     74     else
     75     {
     76         for (vector<CPiece *>::iterator it = m_vecPiece.begin(); it != m_vecPiece.end(); ++it)
     77         {
     78             if ((*it)->GetColor() == color)
     79         {
     80             pPiece = *it;
     81             break;
     82         }
     83         }
     84         if (pPiece == NULL)
     85         {
     86         pPiece = new CGomoku(color);
     87         m_vecPiece.push_back(pPiece);
     88         }
     89      }
     90         return pPiece;
     91     }    
     92 
     93     ~CPieceFactory()
     94     {
     95         for (vector<CPiece *>::iterator it = m_vecPiece.begin(); it != m_vecPiece.end(); ++it)
     96         {
     97             if (*it != NULL)
     98         {
     99         delete *it;
    100         *it = NULL;
    101         }
    102     }
    103     }
    104 
    105 private:
    106     vector<CPiece *> m_vecPiece;
    107 };
    108 
    109 class CChessboard
    110 {
    111 public:
    112     void Draw(CPiece *piece)
    113     {
    114     if (piece->GetColor())
    115     {
    116             cout<<"Draw a White"<<" at ("<<piece->GetPoint().x<<","<<piece->GetPoint().y<<")"<<endl;
    117     }
    118     else
    119     {
    120         cout<<"Draw a Black"<<" at ("<<piece->GetPoint().x<<","<<piece->GetPoint().y<<")"<<endl;
    121     }
    122     m_mapPieces.insert(pair<POINT, CPiece *>(piece->GetPoint(), piece));
    123     }
    124 
    125     void ShowAllPieces()
    126     {
    127         for (map<POINT, CPiece *>::iterator it = m_mapPieces.begin(); it != m_mapPieces.end(); ++it)
    128     {
    129             if (it->second->GetColor())
    130         {
    131         cout<<"("<<it->first.x<<","<<it->first.y<<") has a White chese."<<endl;
    132         }
    133         else
    134         {
    135         cout<<"("<<it->first.x<<","<<it->first.y<<") has a Black chese."<<endl;
    136         }
    137     }
    138     }
    139 
    140 private:
    141     map<POINT, CPiece *> m_mapPieces;
    142 };
    143 
    144 int main()
    145 {
    146     CPieceFactory *pPieceFactory = new CPieceFactory();
    147     CChessboard *pCheseboard = new CChessboard();
    148 
    149     // The player1 get a white piece from the pieces bowl
    150     CPiece *pPiece = pPieceFactory->GetPiece(WHITE);
    151     pPiece->SetPoint(POINT(2, 3));
    152     pCheseboard->Draw(pPiece);
    153 
    154     // The player2 get a black piece from the pieces bowl
    155     pPiece = pPieceFactory->GetPiece(BLACK);
    156     pPiece->SetPoint(POINT(4, 5));
    157     pCheseboard->Draw(pPiece);
    158 
    159     // The player1 get a white piece from the pieces bowl
    160     pPiece = pPieceFactory->GetPiece(WHITE);
    161     pPiece->SetPoint(POINT(2, 4));
    162     pCheseboard->Draw(pPiece);
    163 
    164     // The player2 get a black piece from the pieces bowl
    165     pPiece = pPieceFactory->GetPiece(BLACK);
    166     pPiece->SetPoint(POINT(3, 5));
    167     pCheseboard->Draw(pPiece);
    168 
    169     /*......*/
    170 
    171     //Show all cheses
    172     cout<<"Show all cheses"<<endl;
    173     pCheseboard->ShowAllPieces();
    174 
    175     if (pCheseboard != NULL)
    176     {
    177      delete pCheseboard;
    178     pCheseboard = NULL;
    179     }
    180     if (pPieceFactory != NULL)
    181     {
    182     delete pPieceFactory;
    183     pPieceFactory = NULL;
    184     }
    185 }

    内部状态包括棋子的颜色,外部状态包括棋子在棋盘上的位置。最终,我们省去了多个实例对象存储棋子颜色的空间,从而达到了空间的节约。

    在上面的代码中,我建立了一个CCheseboard用于表示棋盘,棋盘类中保存了放置的黑色棋子和白色棋子;这就相当于在外部保存了共享对象的外部状态;对于棋盘对象,我们是不是又可以使用享元模式呢?再设计一个棋局类进行管理棋盘上的棋子布局,用来保存外部状态。对于这个,这里不进行讨论了。

     

    优点

    享元模式可以避免大量非常相似对象的开销。在程序设计时,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例数据除了几个参数外基本都是相同的,使用享元模式就可以大幅度地减少对象的数量。

     

    使用场合

    Flyweight模式的有效性很大程度上取决于如何使用它以及在何处使用它。当以下条件满足时,我们就可以使用享元模式了。

    1. 一个应用程序使用了大量的对象;
    2. 完全由于使用大量的对象,造成很大的存储开销;
    3. 对象的大多数状态都可变为外部状态;
    4. 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。

     

    扩展

    之前总结了组合模式组合模式,现在回过头来看看,享元模式就好比在组合模式的基础上加上了一个工厂类,进行共享控制。是的,组合模式有的时候会产生很多细粒度的对象,很多时候,我们会将享元模式和组合模式进行结合使用。

     

    总结

    使用享元模式可以避免大量相似对象的开销,减小了空间消耗;而空间的消耗是由以下几个因素决定的:

    1. 实例对象减少的数目;
    2. 对象内部状态的数目;对象内部状态越多,消耗的空间也会越少;
    3. 外部状态是计算的还是存储的;由于外部状态可能需要存储,如果外部状态存储起来,那么空间的节省就不会太多。

    共享的Flyweight越多,存储节约也就越多,节约量随着共享状态的增多而增大。当对象使用大量的内部及外部状态,并且外部状态是计算出来的而非存储的时候,节约量将达到最大。所以,可以使用两种方法来节约存储:用共享减少内部状态的消耗;用计算时间换取对外部状态的存储。

    同时,在实现的时候,一定要控制好外部状态与共享对象的对应关系,比如我在代码实现部分,在CCheseboard类中使用了一个map进行彼此之间的映射,这个映射在实际开发中需要考虑的。

  • 相关阅读:
    使非标准 Win32 控件或自画控件也具有 Windows XP 的界面风格
    MapInfo格式到ArcInfo格式的转换
    DICOM医学图像文件格式
    香港身份证
    Cheap Tricks: Let's Talk About METADATA TypeLibs
    ASP中使用ADO访问数据源
    DirectX 9 编程 DirectX窗口
    3DES Source Code
    OLEDB Resource(Session) Pooling (在Ado开发中使用连接池)
    《仙剑奇侠传4》仙剑问答全答案
  • 原文地址:https://www.cnblogs.com/ring1992/p/9593235.html
Copyright © 2020-2023  润新知