享元模式
享元模式是一种结构设计模式, 它允许你在消耗少量内存的情况下支持大量对象。
简单理解: 一类成员很多,创建这个对象消耗资源,需要在实际场景中反复创建和销毁。消耗的内存更大。
如果此时设计了一个对象池,以缓存某个对象,软件在使用时应用,不需要时回收。可以重复使用对象,并多次创建和销毁对象。
意义
从以上解释可以得出结论,元享受模式主要限制了在资源有限的情况下创建大量对象的行为。通过对现有对象的初始化,达到与创建对象相同的效果。
应用场景
目前开发板只有三个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: 客户代码。
源码实现
- 编译环境: Linux环境
- 语言: C 语言
- 编译命令: make
Flyweight/ ├── led.cc ├── led_factory.cc ├── led_factory.h ├── led.h ├── main.cc └── Makefile
- led: Led实现通用功能。
- led_factory: led实现资源管理工厂。
- Makefile: 编译工具。
- main: 客户代码。
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管脚等硬件信息。
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资源。
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 mLedTablelocation];
} else {
return NULL;
}
}
- 在用户申请Led资源时,先从对象缓存池遍历是否存在可用Led对象,返回给用户。
- 若对象池缓存的Led对象都为busy,判断对象池Led对象个数是否等于Led资源个数。若小于,继续创建Led对象返回给用户,并加入对象池。
- 若对象池中Led对象都busy,且对象池中Led对象个数等于Led资源个数。返回用户无可用的Led对象。
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对象,都被占用时,再申请会返回失败。
测试效果
$ ./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,这里仅是指一种模式)。