前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++设计模式 - 享元模式

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

作者头像
开源519
发布2022-12-01 16:05:51
3090
发布2022-12-01 16:05:51
举报
文章被收录于专栏:开源519

前言

对于世界而言,你是一个人;但是对于某个人,你是她的整个世界。

-- 《水晶之恋》

享元模式

❝享元模式是一种结构型设计模式, 它允许你在消耗少量内存的情况下支持大量对象。 ❞

简单的理解: 一个类的成员非常多,创建此对象很消耗资源,在实际场景中又需要反复创建和销毁该对象。所消耗的内存,就更加庞大。

如果此时设计一个对象池,里面缓存一定的对象,软件在用时申请,不用时回收。就能实现对象的重复利用,而多次创建和销毁对象。

意义

从上述的解释可以总结出,享元模式主要在资源有限的情况下,对创建大量对象行为的一种约束。通过初始化已有的对象,达到与创建对象一样的效果。

应用场景

当前开发板仅存在3个Led灯,其中一个被用作Power指示灯,另两个可被用作软件调试或功能灯,设计Led资源管理,避免使用Led时发生冲突。(消耗的内存不方便演示,此处用Led资源的消耗模拟内存消耗)

分析

板子Led资源有限,其中一个又被指定为Power灯不可被占用,那么仅剩两个Led可被使用。软件上可分为UnShare Led 和 Share Led,其中假设两个Shared Led分别为Led 1和Led2。

如果不对Led资源进行管理,可能会出现如下情况:

  • 各个模块在使用Led时,并不清楚当前Led是否被使用。很可能两个模块同时使用Led 1,导致Led 1显示紊乱,不符合预期。
  • 被用作Power指示灯的Led不允许被其他模块使用,但是实际场景中,可能某些模块误用了Power 指示灯。

因为Led的操作都是一样的,只是具体资源有差异。可引用享元模式根据现有Led资源创建若干个Led对象放入对象工厂,各个模块从对象工厂申请即可使用。

类图

根据上述分析结合享元模式构建类图:

  • CLed: Led功能通用接口类。
  • CShareLed、CUnsharedLed: 具体Led功能的实现类。
  • CledFactory: 管理当前Led资源的工厂类。模块通过工厂获取Led资源。
  • Client: 客户端代码。

源码实现

「编程环境」

  1. 编译环境: Linux环境
  2. 语言: C++语言
  3. 编译命令: make

「工程结构」

代码语言:javascript
复制
Flyweight/
├── led.cc
├── led_factory.cc
├── led_factory.h
├── led.h
├── main.cc
└── Makefile
  • led: Led通用功能实现。
  • led_factory: led资源管理工厂实现。
  • Makefile: 编译工具。
  • main: 客户端代码。

「Led通用功能接口」

代码语言:javascript
复制
class CLed
{
public:
    CLed () {}
    virtual ~CLed() {}
    void SetStatus(ELedStatus status);
    ELedStatus  GetStatus();
    void SetUsedMode(EUsedMode mode);
    EUsedMode  GetUsedMode();
    void SetPermission(EPermission permission);
    EPermission  GetPermission();
    void SetFrequency(int freq);
    void SetIndex(int value);
    void UpdateLed(EPermission permission, EUsedMode mode, int freq);
    virtual void Start();
    virtual void Stop();
    virtual void Relase();

private:
    int         mIndex;
    int         mFreq;
    ELedStatus  mStatus;
    EUsedMode   mUsedMode;
    EPermission mPermission;
};

此类主要实现Led的通用功能,不涉及具体的Led管脚等硬件信息。

「Led管理工厂接口」

代码语言:javascript
复制
class CLedFactory
{
public:
    static CLedFactory* GetInstance();

    CLed* GetLed(EPermission permission, EUsedMode mode = LED_MODE_DEFAULT, int freq = 10);

private:
    std::map<int, CLed *> mLedTable;///Led享元对象
    CLedFactory() {}                ///< 单例模式
    ~CLedFactory() {}
};

此类主要用于缓存Led对象,目前仅缓存三个对象: Unshare Led、Share Led(Led1、Led2)。客户端通过GetLed接口获取未被使用的Led资源。

「工厂获取Led接口实现」

代码语言:javascript
复制
CLed* CLedFactory::GetLed(EPermission permission, EUsedMode mode, int freq)
{
    int i, sum = 0, location = -1;

    switch (permission)
    {
        case LED_UNSHARED:
        {
            // 返回Power Led对象
            if (CUnsharedLed::GetInstance()->GetStatus() == LED_IDLE) {
                CUnsharedLed::GetInstance()->SetStatus(LED_BUSY);
                return CUnsharedLed::GetInstance();
            } else {
                return NULL;
            }
        }
        break;

        case LED_SHARED:
        {
            sum = mLedTable.size();
            // loop: 遍历所有LED状态,返回空闲的LED
            for (i = 0; i < sum; i++) {
                if ( mLedTable[i]->GetStatus() == LED_IDLE)
                {
                    location = i;
                    goto RET;       // 找到空闲的LED, 返回当前下标
                }
            }

            // 当前存在的LED对象都处于busy状态,
            // 若Led资源未到上限,则创建新的Led对象。
            if (i == sum && sum < MAX_SHARED_LED) {
                FACTORY_LOGD("Create Shared Led %d!\n", i);
                mLedTable.insert(std::pair<int, CLed*>(i, new CSharedLed()));
                location = i;
            }
        }
        break;

        default:
        break;
    }

RET:
    if (location >= 0) {
        mLedTable[location]->UpdateLed(permission, mode, freq);
        return mLedTable[location];
    } else {
        return NULL;
    }
}
  • 在用户申请Led资源时,先从对象缓存池遍历是否存在可用Led对象,返回给用户。
  • 若对象池缓存的Led对象都为busy,判断对象池Led对象个数是否等于Led资源个数。若小于,继续创建Led对象返回给用户,并加入对象池。
  • 若对象池中Led对象都busy,且对象池中Led对象个数等于Led资源个数。返回用户无可用的Led对象。

「客户端代码」

代码语言:javascript
复制
int main(int argc, char *argv[])
{
    CLedFactory *theLedFactory = CLedFactory::GetInstance();
    if (!theLedFactory) {
        MAIN_LOGE("Get Led Factory failed!\n");
        return -1;
    }

    // ------------------- UnShare Led Test ------------------------
    MAIN_LOG("-> UnShare led test 1st!\n");
    CLed *thePowerLed1 = theLedFactory->GetLed(LED_UNSHARED);
    if (thePowerLed1) {
        thePowerLed1->Start();
    } else {
        MAIN_LOGE("Get Power Led failed!\n");
    }

    thePowerLed1->Relase(); // 当前使用释放,才可被再次使用

    MAIN_LOG("\n-> UnShare led test 2nd!\n");
    CLed *thePowerLed2 = theLedFactory->GetLed(LED_UNSHARED);
    if (thePowerLed2) {
        thePowerLed2->Start();
    } else {
        MAIN_LOGE("Get Power Led failed!\n");
    }
    // -------------------      Test End    ------------------------

    // -------------------  Share Led Test  ------------------------
    MAIN_LOG("\n-> Share led test 1st!\n");
    CLed *theLed1 = theLedFactory->GetLed(LED_SHARED, LED_MODE_HORSE);
    if (theLed1) {
        theLed1->Start();
    } else {
        MAIN_LOGE("Get Led failed!\n");
    }

    MAIN_LOG("\n-> Share led test 2nd!\n");
    CLed *theLed2 = theLedFactory->GetLed(LED_SHARED, LED_MODE_BREATH);
    if (theLed2) {
        theLed2->Start();
    } else {
        MAIN_LOGE("Get Led failed!\n");
    }

    // Share Led 仅有两个,若不释放占用的Led。Led3就拿不到资源
    MAIN_LOG("\n-> Share led test 3rd!\n");
    CLed *theLed3 = theLedFactory->GetLed(LED_SHARED, LED_MODE_BREATH);
    if (theLed3) {
        theLed3->Start();
    } else {
        MAIN_LOGE("Get Led failed!\n");
    }
    // -------------------      Test End    ------------------------

    return 0;
}
  • 对Unshare Led测试: 在thePowerLed1被释放后才可被thePowerLed2申请使用。
  • 对Share Led测试: 仅存在两个Led对象,都被占用时,再申请会返回失败。

测试效果

代码语言:javascript
复制
$ ./exe 
-> UnShare led test 1st!
Power Led (pin10) ON. 
Power Led (pin10) OFF.
Relase power Led (pin10).

-> UnShare led test 2nd!
Power Led (pin10) ON. 

-> Share led test 1st!
Led 0 (pin20) Start. Mode: Horse Mode 

-> Share led test 2nd!
Led 1 (pin21) Start. Mode: Breath Mode 

-> Share led test 3rd!
74 Main E: Get Led failed!

总结

  • 「享元模式」的实现方式主要是,创建一定个数的对象放到对象池缓存。当用户需要使用时,从对象池申请;当用户不再使用时,回收至对象池。
  • 在《设计模式》中指出,「享元模式」可使用在类变量过多,反复创建/销毁会消耗资源的场景下。但是在笔者思考后发现,也可以用于对共享资源的管理上,于是有了本文。
  • 在其他大佬总结中,很少看到代码中有回收动作。感觉是一个对象可以同时被多个模块使用,只是在使用的时候初始化一下对象内部状态。那么这种做法,难道不会影响上个使用此对象还在运行的模块吗?至少对于本篇Led的使用会有影响,例如上个模块还在呼吸灯模式,下个模块转换成跑马灯模式,就会影响到呼吸灯(当然跑马灯需要多个Led,这里仅是指一种模式)。

最后

用心感悟,认真记录,写好每一篇文章,分享每一框干货。

更多文章内容包括但不限于C/C++、Linux、开发常用神器等,可进入“开源519”公众号聊天界面输入“文章目录”, 或菜单栏选择“文章目录”查看所有文章。后台聊天输入本文标题,可查看源码。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-03-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开源519 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 享元模式
    • 意义
      • 应用场景
        • 分析
          • 类图
            • 源码实现
              • 测试效果
                • 总结
                  • 最后
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档