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

时间:2022-09-14 18:07:25
本文版权归果冻说所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。
如果这篇文章对你有帮助,你可以请我喝杯咖啡。

» 本文链接:http://www.jellythink.com/archives/295
» 订阅本站:http://www.jellythink.com/feed

» 转载请注明来源:果冻说 » 《C++设计模式——享元模式》
 

前言

无聊的时候,也去QQ游戏大厅玩五子棋或者象棋;作为程序员,看到一个产品,总要去想想它是怎么设计的,怎么完成的,我想这个是所有程序员都会做的事情吧(强迫症???)。有的时候,想完了,还要做一个DEMO出来,才能体现自己的NB,然后还有点小成就感。

在玩五子棋或象棋的时候,我就想过,腾讯那帮伙计是怎么做的呢?五子棋的棋子有黑白两色,难道每次放一个棋子就new一个对象么?象棋有车、马、相、士、帅、炮和兵,是不是每盘棋都要把所有的棋子都new出来呢?如果真的是每一个棋子都new一个,那么再加上那么多人玩;那要new多少对象啊,如果是这样做的话,我想有多少服务器都是搞不定的,可能QQ游戏大厅会比12306还糟糕。那腾讯那帮伙计是如何实现的呢?那就要说到今天总结的享元模式了。

什么是享元模式?

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

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

UML类图

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

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对象,这可以保证对它们适当地进行共享;由于共享一个实例,所以在创建这个实例时,就可以考虑使用单例模式来进行实现。

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

代码实现

#include <iostream>
#include <map>
#include <vector>
using namespace std;

typedef struct pointTag
{
    int x;
    int y;

    pointTag(){}
    pointTag(int a, int b)
    {
        x = a;
    y = b;
    }

    bool operator <(const pointTag& other) const
    {
        if (x < other.x)
        {
            return true;
        }
        else if (x == other.x)
        {
            return y < other.y;
        }

        return false;
    }
}POINT;

typedef enum PieceColorTag
{
    BLACK,
    WHITE
}PIECECOLOR;

class CPiece
{
public:
    CPiece(PIECECOLOR color) : m_color(color){}
    PIECECOLOR GetColor() { return m_color; }

    // Set the external state
    void SetPoint(POINT point) { m_point = point; }
    POINT GetPoint() { return m_point; }

protected:
    // Internal state
    PIECECOLOR m_color;

    // external state
    POINT m_point;
};

class CGomoku : public CPiece
{
public:
    CGomoku(PIECECOLOR color) : CPiece(color){}
};

class CPieceFactory
{
public:
    CPiece *GetPiece(PIECECOLOR color)
    {
        CPiece *pPiece = NULL;
    if (m_vecPiece.empty())
    {
        pPiece = new CGomoku(color);
        m_vecPiece.push_back(pPiece);
    }
    else
    {
        for (vector<CPiece *>::iterator it = m_vecPiece.begin(); it != m_vecPiece.end(); ++it)
        {
            if ((*it)->GetColor() == color)
        {
            pPiece = *it;
            break;
        }
        }
        if (pPiece == NULL)
        {
        pPiece = new CGomoku(color);
        m_vecPiece.push_back(pPiece);
        }
     }
        return pPiece;
    }    

    ~CPieceFactory()
    {
        for (vector<CPiece *>::iterator it = m_vecPiece.begin(); it != m_vecPiece.end(); ++it)
        {
            if (*it != NULL)
        {
        delete *it;
        *it = NULL;
        }
    }
    }

private:
    vector<CPiece *> m_vecPiece;
};

class CChessboard
{
public:
    void Draw(CPiece *piece)
    {
    if (piece->GetColor())
    {
            cout<<"Draw a White"<<" at ("<<piece->GetPoint().x<<","<<piece->GetPoint().y<<")"<<endl;
    }
    else
    {
        cout<<"Draw a Black"<<" at ("<<piece->GetPoint().x<<","<<piece->GetPoint().y<<")"<<endl;
    }
    m_mapPieces.insert(pair<POINT, CPiece *>(piece->GetPoint(), piece));
    }

    void ShowAllPieces()
    {
        for (map<POINT, CPiece *>::iterator it = m_mapPieces.begin(); it != m_mapPieces.end(); ++it)
    {
            if (it->second->GetColor())
        {
        cout<<"("<<it->first.x<<","<<it->first.y<<") has a White chese."<<endl;
        }
        else
        {
        cout<<"("<<it->first.x<<","<<it->first.y<<") has a Black chese."<<endl;
        }
    }
    }

private:
    map<POINT, CPiece *> m_mapPieces;
};

int main()
{
    CPieceFactory *pPieceFactory = new CPieceFactory();
    CChessboard *pCheseboard = new CChessboard();

    // The player1 get a white piece from the pieces bowl
    CPiece *pPiece = pPieceFactory->GetPiece(WHITE);
    pPiece->SetPoint(POINT(, ));
    pCheseboard->Draw(pPiece);

    // The player2 get a black piece from the pieces bowl
    pPiece = pPieceFactory->GetPiece(BLACK);
    pPiece->SetPoint(POINT(, ));
    pCheseboard->Draw(pPiece);

    // The player1 get a white piece from the pieces bowl
    pPiece = pPieceFactory->GetPiece(WHITE);
    pPiece->SetPoint(POINT(, ));
    pCheseboard->Draw(pPiece);

    // The player2 get a black piece from the pieces bowl
    pPiece = pPieceFactory->GetPiece(BLACK);
    pPiece->SetPoint(POINT(, ));
    pCheseboard->Draw(pPiece);

    /*......*/

    //Show all cheses
    cout<<"Show all cheses"<<endl;
    pCheseboard->ShowAllPieces();

    if (pCheseboard != NULL)
    {
     delete pCheseboard;
    pCheseboard = NULL;
    }
    if (pPieceFactory != NULL)
    {
    delete pPieceFactory;
    pPieceFactory = NULL;
    }
}

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

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

优点

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

使用场合

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

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

扩展

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

总结

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

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

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

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

好了,享元模式就总结到这里了。希望大家和我分享你对设计模式的理解。我坚信:分享使我们更进步。
PS:至于腾讯那帮伙计到底是如何实现QQ游戏大厅的,我也不知道,这里也完全是猜测的,请不要以此为基准。

2014年1月7日 于大连,东软。

本文版权归果冻说所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。
如果这篇文章对你有帮助,你可以请我喝杯咖啡。

» 本文链接:http://www.jellythink.com/archives/295
» 订阅本站:http://www.jellythink.com/feed

» 转载请注明来源:果冻说 » 《C++设计模式——享元模式》

C++设计模式——享元模式的更多相关文章

  1. 8&period; 星际争霸之php设计模式--享元模式

    题记==============================================================================本php设计模式专辑来源于博客(jymo ...

  2. java设计模式——享元模式

    一. 定义与类型 定义:提供了减少对象数量从而改善应用所需的对象结构的方式,运用共享技术有效地支持大量细粒度的对象 类型:结构性 二. 使用场景 (1)  常常应用于系统底层的开发,以便解决系统的性能 ...

  3. &lbrack;工作中的设计模式&rsqb;享元模式模式FlyWeight

    一.模式解析 Flyweight在拳击比赛中指最轻量级,即“蝇量级”或“雨量级”,这里选择使用“享元模式”的意译,是因为这样更能反映模式的用意.享元模式是对象的结构模式.享元模式以共享的方式高效地支持 ...

  4. JAVA 设计模式 享元模式

    用途 享元模式 (Flyweight) 运用共享技术有效地支持大量细粒度的对象. 享元模式是一种结构型模式. 结构

  5. 深入浅出设计模式——享元模式(Flyweight Pattern)

    模式动机 面向对象技术可以很好地解决一些灵活性或可扩展性问题,但在很多情况下需要在系统中增加类和对象的个数.当对象数量太多时,将导致运行代价过高,带来性能下降等问题.享元模式正是为解决这一类问题而诞生 ...

  6. Java设计模式-享元模式(Flyweight)

    享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用. FlyWeightFactory负责创建和管理享元单元,当一个客户端请求时,工厂需要检查 ...

  7. java设计模式---享元模式

    享元模式 顾名思义:共享元对象.如果在一个系统中存在多个相同的对象,那么只需要共享一份对象的拷贝,而不必为每一次使用创建新的对象. 享元模式是为数不多的.只为提升系统性能而生的设计模式.它的主要作用就 ...

  8. 结合JDK源码看设计模式——享元模式

    前言 在说享元模式之前,你一定见到过这样的面试题 public class Test { public static void main(String[] args) { Integer a=Inte ...

  9. 设计模式-享元模式&lpar;FlyWeight&rpar;

    一.概念 享元模式是对象的结构模式,它以共享的方式高效的支持大量的细粒度对象,减少对象的数量,并达到节约内存的目的. 享元对象能够做到共享的关键,主要是区分了内部状态和外部状态,内部状态是对象是在建立 ...

随机推荐

  1. JS操作页面

    DOM操作 1 windows对象操作 属性(值或者子对象): opener:打开当前窗口的源窗口,如果当前窗口是首次启动浏览器打开的,则opener是null.可以利用这属性来关闭源窗口 方法(函数 ...

  2. Silverlight控件——如何提升应用程序信任度与问题解决

    从silverlight5开始,可以在项目设置中勾选“在浏览器内运行时需要提升的信任”来达到在浏览器内运行提权silverlight客户端的目的,在个特性很有用处. 可我使用这个功能时遇到了一个奇怪的 ...

  3. autolayout也会锁死

    This application is modifying the autolayout engine from a background thread, which can lead to engi ...

  4. Xcode升级后插件失效的原理与修复办法

    转载:http://joeshang.github.io/2015/04/10/fix-xcode-upgrade-plugin-invalid/ Xcode 的插件大大丰富了 Xcode 的功能,而 ...

  5. android开发之Parcelable使用详解

    想要在两个activity之间传递对象,那么这个对象必须序列化,android中序列化一个对象有两种方式,一种是实现Serializable接口,这个非常简单,只需要声明一下就可以了,不痛不痒.但是a ...

  6. java小数点的两种处理方法

    1. java.text.DecimalFormat;        //此方法为四舍五入 例如:DecimalFormat df = new DecimalFormat("#.0&quot ...

  7. &lbrack;Swift&rsqb;LeetCode741&period; 摘樱桃 &vert; Cherry Pickup

    In a N x N grid representing a field of cherries, each cell is one of three possible integers. 0 mea ...

  8. svn &colon; Can not Parse lock &sol; entries hashfile错误解决办法

    svn服务器死机重启之后,锁定文件的时候出下面的提示: Malformed file svn: Can't parse lock/entries hashfile '/data/svn/svnroot ...

  9. 【转载】基于MFC的ActiveX控件开发&lpar;1&rpar;

    原文:http://iysm.net/?p=114 ActiveX 控件是基于组件对象模型 (COM) 的可重用软件组件,广泛应用于桌面及Web应用中.在VC下ActiveX控件的开发可以分为三种,一 ...

  10. QWidget类参考

    QWidget类是所有用户界面对象的基类. 详情请见…… #include <qwidget.h> 继承QObject和QPaintDevice. 被QButton.QFrame.QDia ...