我的另一篇文章 使用 C++ template 进行多厂商接口的适配,已经描述过如何在编译期通过模板函数区分产品的不同行为,但模板函数无法在代码框架层面支持多客户(产品)。本篇文章,从类设计的角度阐述一下,如何使用 template 让一套代码兼容多个产品。
需求分析
我的聊天机器人有三个语言包:
中文class ChinesePackage
;
俄文class RussianPackage
;
英文class EnglishPackage
;
针对不同的客户,有三个产品型号,要支持不同的语言包:
默认熊猫版本RobotPanda
,要支持全部中文+俄文+英文语言包。
北极熊版本RobotBear
,要支持中文+俄文。
小绵羊版本RobotSheep
,支持中文+英文。
引子:conditional
首先看一下标准库中用于类型选择的 conditional,在《The C++ Programming Language》一书中有比较详细的例子。
template<bool _Cond, typename _Iftrue, typename _Iffalse>
struct conditional
{
typedef _Iftrue type;
};
// Partial specialization for false.
template<typename _Iftrue, typename _Iffalse>
struct conditional<false, _Iftrue, _Iffalse>
{
typedef _Iffalse type;
};
复制代码
conditional 的实现很容易理解,就是通过第一个参数_Cond 在编译期做类型选择。
但 bool 值只有两个取值 true/false,也就是我们的类型只有两种选择,能不能有三种或多种呢?这里我们来个举一反三,把 bool _Cond 改为 int _Cond,是不是就可以支持无限个选择了呢?
准备工作
脚本与产品类型宏
为每个产品创建一个编译脚本,传递对应的宏到源码:
make_panda.sh: -DROBOT_TYPE=0x4348
make_bear.sh: -DROBOT_TYPE=0x5255
make_sheep.sh: -DROBOT_TYPE=0x454E
产品类型常量
源码中也要定义对应的常量:
const int ROBOT_TYPE_CH = 0x4348;
const int ROBOT_TYPE_RU = 0x5255;
const int ROBOT_TYPE_EN = 0x454E;
复制代码
语言包
//用于上层业务选择语言
enum LANGUAGE_CHOICE
{
LAN_CH = 0,
LAN_EN,
LAN_RU,
LAN_MAX
};
class LanguagePackage
{
virtual void Speak() {}
}
class ChinesePackage : public LanguagePackage
{
void Speak() override
{
//讲中文
}
};
class RussianPackage : public LanguagePackage
{
void Speak() override
{
//讲俄文
}
};
class EnglishPackage : public LanguagePackage
{
void Speak() override
{
//讲英文
}
};
复制代码
举一反三
如果沿用 conditional 的实现方式,三选一的模板就是如下形式:
template<int RobotType, class T_Panda, class T_Bear, class T_Sheep>
struct RobotModelType
{
using Type = T_Panda;
};
template<>
struct RobotModelType<ROBOT_TYPE_RU>
{
using Type = T_Bear;
};
template<>
struct RobotModelType<ROBOT_TYPE_EN>
{
using Type = T_Sheep;
};
using RobotType = RobotModelType<ROBOT_TYPE, RobotPanda, RobotBear, RobotSheep>::Type;
复制代码
但这里有个代码扩展的问题,如果我们再增加新的产品型号 RobotYY,RobotModelType 是不是要增加模板参数呢?如果新增 N 个产品型号呢?
举一反 N
那不妨思维灵活一些,我的代码是在做工程,不是在做 C++标准库,所以不需要兼容不确定的产品类型,只需要兼容我自己设定的 Robot 类型就可以啦。这样就简单了,把要选择的类型从模板参数列表中去掉,直接通过特化来实现。
RobotPanda
template<int RobotType>
struct RobotPanda
{
void Config(LANGUAGE_CHOICE lan_choice)
{
switch(lan_choice)
{
case LAN_CH:
m_LanPackage = &m_chinese;
break;
case LAN_RU:
m_LanPackage = &m_russian;
break;
case LAN_EN:
m_LanPackage = &m_english;
break;
default:
//报错,不支持
break;
}
}
ChinesePackage m_chinese;
RussianPackage m_russian;
EnglishPackage m_english;
LanguagePackage* m_LanPackage = &m_chinese;
};
复制代码
RobotBear
template<int RobotType>
struct RobotBear
{
void Config(LANGUAGE_CHOICE lan_choice)
{
switch(lan_choice)
{
case LAN_RU:
m_LanPackage = &m_russian;
break;
case LAN_CH:
m_LanPackage = &m_chinese;
break;
default:
//报错,不支持
break;
}
}
RussianPackage m_russian;
ChinesePackage m_chinese;
LanguagePackage* m_LanPackage = &m_chinese;
};
复制代码
RobotSheep
template<int RobotType>
struct RobotSheep
{
void Config(LANGUAGE_CHOICE lan_choice)
{
switch(lan_choice)
{
case LAN_EN:
m_LanPackage = &m_english;
break;
case LAN_CH:
m_LanPackage = &m_chinese;
break;
default:
//报错,不支持
break;
}
}
EnglishPackage m_english;
ChinesePackage m_chinese;
LanguagePackage* m_LanPackage = &m_chinese;
};
复制代码
RobotModelType 重磅来袭
/* 编译期类型选择. 默认 ROBOT_TYPE_CH. */
template<int RobotType>
struct RobotModelType
{
using Type = RobotPanda<ModelType>;
};
template<>
struct RobotModelType<ROBOT_TYPE_EN>
{
using Type = RobotSheep<ROBOT_TYPE_EN>;
};
template<>
struct RobotModelType<ROBOT_TYPE_RU>
{
using Type = RobotBear<ROBOT_TYPE_RU>;
};
复制代码
template RobotModelType
是本文的重点,理论上它支持无限种产品扩展,新增产品型号时,只需要增加对应的型号常量和特化就可以了。
RobotXX 之所以也设计为模板,是为了在编译一个产品时,其他的产品代码不被生成,减少代码尺寸,也杜绝代码被反编译泄露的可能。
机器人
一个产品脚本,只编译自己项目的机器人。
class RobotTalker : protected RobotModelType<ROBOT_TYPE>::Type
{
public:
void ConfigPackage(LANGUAGE_CHOICE lan_choice)
{
Config(lan_choice);
}
void Speak()
{
m_LanPackage->Speak();
}
};
复制代码
类图
为了更直观的理解代码框架,我把每个机器人的类图画出来。
熊猫
RobotPanda,要支持全部语言包。
Panda
北极熊
RobotBear,要支持中文+俄文包。
Bear
小绵羊
RobotSheep,支持中文+英文。
Sheep
结语
这就是模板的威力,不用编译宏,不用拉代码分支,一套代码支持多个产品型号,但我不太想把这种做法定义为编译期多态或者编译期的策略模式,思维模式没必要生拉硬套,能解决问题就好。
当多个产品大部分基本功能相同,仅部分功能有差异,需要针对不同需求进行组合时,本篇中的方法可灵活适配。
评论