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

2022-12-01 16:05:51 浏览数 (1)

前言

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

-- 《水晶之恋》

享元模式

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

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

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

意义

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

应用场景

当前开发板仅存在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”公众号聊天界面输入“文章目录”, 或菜单栏选择“文章目录”查看所有文章。后台聊天输入本文标题,可查看源码。

0 人点赞