枚举对比转换思路
需求分析
在Android系统开发中,我们经常会有这样的需求:
a. 首先,我们会在代码中定义许多配置类型,如定义音频输出设备,耳机、扬声器、蓝牙音箱等等,你会想到用枚举,如下:
enum audio_device_t {
AUDIO_DEVICE_OUT_EARPIECE,
AUDIO_DEVICE_OUT_SPEAKER,
....
};
b. 其次,我们在使用时会配置我们的输出设备,你可以在代码或者xml配置文件中配置,通常我们使用时,不一定和定义enum类型一致,如下:
<devicePort tagName="Earpiece" type="AUDIO_DEVICE_OUT_EARPIECE" role="sink"/>
c. 最后,我们会提取xml配置的type字符串类型,去我们定义的enum audio_device_t对比查找,找到我们配置选项,比较简单的做法可以像这么做
struct AudioDevice {
const char *name;
audio_device_t value;
}
const AudioDevice devices[] = {{"AUDIO_DEVICE_OUT_EARPIECE", AUDIO_DEVICE_OUT_SPEAKER},
{"AUDIO_DEVICE_OUT_SPEAKER", AUDIO_DEVICE_OUT_SPEAKER}};
audio_device_t compareAudioDevice(std::string &device){
for(int i = 0; i < sizeof(devices)/sizeof(devices[0]); i++){
if(strcmp(devices[i].name, device.c_str()) == 0){
return devices[i].value;
}
}
return null;
}
如果系统配置数量在少数的话,这样写法是没问题的,如果配置很多,从代码上来看会产生很多重复的代码,主要是compareAudioDevice对比方法比较多,如果减少冗余代码呢?肯定是用模板来减少,Android系统的TypeConvert类就是来做这个事情的,它把上面c步骤里面的属性和方法都封装在一个类里面如下:
template <class Traits>
class TypeConverter
{
public:
//对比方法str是传入的key, result是查找后返回的结果
static bool fromString(const std::string &str, typename Traits::Type &result){
for (size_t i = 0; mTable[i].literal; i++) {
if (strcmp(mTable[i].literal, str.c_str()) == 0) {
ALOGV("stringToEnum() found %s", mTable[i].literal);
result = mTable[i].value;
return true;
}
}
return false;
}
......
protected:
//枚举类型字符串和值的对应关系结构体
struct Table {
const char *literal;
typename Traits::Type value;
};
//存储对应关系的数组
static const Table mTable[];
};
只看上面可能还不是很懂,TypeConverter相当于最上面抽象的一层,还需要看以下具体的定义:
template <typename T>
struct DefaultTraits
{
typedef T Type;
....
};
using DeviceTraits = DefaultTraits<audio_device_t>;
struct OutputDeviceTraits : public DeviceTraits {};
typedef TypeConverter<OutputDeviceTraits> OutputDeviceConverter;
DeviceTraits声明了泛型是audio_device_t,也就是我们的配置定义项,为什么这么做,因为在DefaultTraits我们除了T泛型外,还可以配置其他的方法,方便的调用;
OutputDeviceTraits类型继承DeviceTraits,我觉得不要功能也是可以实现的,后续直接typedef TypeConverter OutputDeviceConverter也可以实现,这么定义我觉得是为了代码易读,后期也方便扩展;比如audio_device_t其实有输入输出类型,不重新定义OutputDeviceTraits类型,易读性不佳且后续扩展不好;
最后一句就好理解了,模板调用;
以上完成后,还需要为mTable赋初值,可以按照我上面的例子赋初值,也可以用一种更好的方式,如下:
//这个宏定义可以理解为输入一个string参数,输出两个参数,一个是字符串string,另一个是string参数本身
#define MAKE_STRING_FROM_ENUM(string) { #string, string }
template <>
const OutputDeviceConverter::Table OutputDeviceConverter::mTable[] = {
MAKE_STRING_FROM_ENUM(AUDIO_DEVICE_NONE),
MAKE_STRING_FROM_ENUM(AUDIO_DEVICE_OUT_EARPIECE),
MAKE_STRING_FROM_ENUM(AUDIO_DEVICE_OUT_SPEAKER),
....
}
最后的最后,我们只需要使用OutputDeviceConverter::fromString()就可以完成对比查找功能了;如果后面又有新的配置项,我们怎么做呢?
像下面这样即可:
using OutputFlagTraits = DefaultTraits<audio_output_flags_t>;
typedef TypeConverter<OutputFlagTraits> OutputFlagConverter;
audio_output_flags_t是我们定义的基本枚举类型,OutputFlagConverter是最终成型可直接使用的类
是不是很简单,以后枚举对比就可以参考这个方案了!
本方案参考了Android系统的TypeConvert类,对它进行了一个小总结!