前言
对于世界而言,你是一个人;但是对于某个人,你是她的整个世界。
-- 《水晶之恋》
享元模式
❝享元模式是一种结构型设计模式, 它允许你在消耗少量内存的情况下支持大量对象。 ❞
简单的理解: 一个类的成员非常多,创建此对象很消耗资源,在实际场景中又需要反复创建和销毁该对象。所消耗的内存,就更加庞大。
如果此时设计一个对象池,里面缓存一定的对象,软件在用时申请,不用时回收。就能实现对象的重复利用,而多次创建和销毁对象。
意义
从上述的解释可以总结出,享元模式主要在资源有限的情况下,对创建大量对象行为的一种约束。通过初始化已有的对象,达到与创建对象一样的效果。
应用场景
当前开发板仅存在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: 客户端代码。
源码实现
「编程环境」
- 编译环境: Linux环境
- 语言: C 语言
- 编译命令: 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”公众号聊天界面输入“文章目录”, 或菜单栏选择“文章目录”查看所有文章。后台聊天输入本文标题,可查看源码。