假设缓存中数据的格式如下所示:
id | name level exp time
下面,我们考虑对数据操作进行封装,先定义一个类
class CCacheData;
在对数据进行操作时,可能需要读写name,于是我们写了一个接口,这个接口会实时更新缓存
class CCacheData
{
SetName(string &name);
};
由于业务需要,我们需要一个接口更新exp,于是又多了一个接口
class CCacheData
{
SetName(string &name);
SetExp(int exp);
};
由于业务需要,我们需要一个接口更新exp的同时,更新time,于是又多了一个接口
class CCacheData
{
SetName(string &name);
SetExp(int exp);
SetExp(int exp, int time);
};
由于业务需要,我们需要一个接口更新level,于是又多了一个接口
class CCacheData
{
SetName(string &name);
SetExp(int exp);
SetExp(int exp, int time);
SetLevel(int level);
};
由于业务需要,我们需要一个接口更新level的同时,更新time,于是又多了一个接口
class CCacheData
{
SetName(string &name);
SetExp(int exp);
SetExp(int exp, int time);
SetLevel(int level);
SetLevel(int level, int time);
};
由于业务需要,我们需要一个接口 ...
........
上面的方式,有几个弊端:
1.接口会随着业务不断增加
2.每次更新都会更新数据源,开销比较大,如果业务需要多次更新时,不能保证原子操作
于是,我们想了一个办法,用一个结构体把数据封装起来,然后定义读写接口,如下所示:
class CCacheData
{
struct stData{
int id;
string name;
int exp;
int level;
int time;
};
Get(stData &data);
Set(stData &data);
};
上面的方式业务简单而且清晰,也解决了第一种封装方式的问题,但存在安全性的问题。
比如,一个调用者,可能在业务逻辑中多次调用,在外部多次修改结构体,很有可能会不小心修改了不应该改的字段,如不小心赋了下初值等,这样set的时候会直接回写,数据就会出错。
于是我们想到一种更好的方式,暴露给调用者的只有接口,而数据结构本身做为一个成员变量保存在类中,如下所示:
class CCacheData
{
struct stData{
int id;
string name;
int exp;
int level;
int time;
};
public:
Query(int id);
Commit();
SetName(string &name);
string GetName();
SetExp(int exp);
int GetExp();
SetLevel(int level);
int GetLevel();
SetTime(int time);
int GetTime();
private:
stData m_data;
};
这样,每次调用时先Query,把数据读出,并给m_data赋值,然后,可以多次调用get和set接口,如果有set过的话,最后只要调用commit接口,就可提交,这种方式有几个优点:
1.保证了原子性,无论是读或写几个字段,都只要两次操作即可完成
2.保证了类成员数据安全,封装等级比较高
3.数据结构调整时(比如添加一个flag字段),由于暴露给上层的只是接口,所以调用者代码无需修改
总结:
对缓存或数据库数据操作进行封装时,一定要注意原子性、通用性和可扩展性,最好给上层提供统一的接口类或实现,这样,才能保证代码不会随业务复杂性增加而快速暴涨,同时也能保证代码的清晰与易用性。
上面的封装采用了Facade模式,其实设计模式在编码中处处可以体现,这也对我们开发人员提出更高的要求,如果能合理、适度的使用设计模式,使自己的代码更简洁、更优美。