写点什么

template 竟能使一套 C++ 代码支持多个客户?

作者:老王同学
  • 2023-04-04
    广东
  • 本文字数:2807 字

    阅读完需:约 9 分钟

我的另一篇文章 使用 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

结语

这就是模板的威力,不用编译宏,不用拉代码分支,一套代码支持多个产品型号,但我不太想把这种做法定义为编译期多态或者编译期的策略模式,思维模式没必要生拉硬套,能解决问题就好。

当多个产品大部分基本功能相同,仅部分功能有差异,需要针对不同需求进行组合时,本篇中的方法可灵活适配。


发布于: 刚刚阅读数: 4
用户头像

老王同学

关注

多读书,勤跑步,少做梦 2020-04-30 加入

还未添加个人简介

评论

发布
暂无评论
template竟能使一套C++代码支持多个客户?_c++_老王同学_InfoQ写作社区