1、前言

在实际的编程中,我们经常会使用到switch..case语句,这通常也是对一长串if..else if语句的优化。对于一些简单的情况(只每个case代码中代码长度不会很长,而且case分之并不多的情况),用switch..case语句即可,此时代码的可读性并不会很差,结构也算是清晰。但是一旦case分支数目众多,每个case语句块中代码长度也很长,这时对于维护这段代码的同学则是个噩梦了(本人就遇到过一段代码,case分支有近20个,每个case语句块中,代码长度均有几十上百行,有的甚至有几百行之多。看到这段代码时,我也是醉了,果断对它进行重构)。

对于switch..case语句有几种常用的方式,下面我一一为大家介绍。在正式介绍之前,我们优化的目标代码如下所示:

struct Param
{...//待传入FuncToOptimal函数的参数
}enum EDataType
{Avg,Min,Max,TI,Tab ,...    //更多其他的枚举值
};double FuncToOptimal(EDataType eDataTypeToCompute, const Param& inParam)
{double dRetValue = 0;...    //局部变量定义switch (eDataTypeToCompute){case Avg:for (...){do{... //(1)Avg情况下的计算内容} while (...);}break;case Min:for (...){do{... //(2)Min情况下的计算内容} while (...);}break;case Max:for (...){do{... //(3)Max情况下的计算内容} while (...);}break;case TI:for (...){do{... //(4)TI情况下的计算内容} while (...);}break;case Tab:for (...){do{... //(5)Tab情况下的计算内容} while (...);}break;
case ...:    //更多其他的case...
break;default:for (...){do{... //(6)default情况下的计算内容} while (...);}break;}return dRetValue;
}

以上只是示例代码,在实际中有可能会比这个更为复杂,一个函数就有可能数千行。对于一个对于业务不熟悉的人,看到这样的代码自然是没有兴趣或者是没有勇气继续往下看的,因为这种代码的可读性是非常差的,后期维护成本会很高,在实际的工作中我们应该尽可能的避免这种代码的出现。对于以上示例代码,有以下三种优化方式:抽取法、继承与多态、跳表法,以下分别进行介绍:

2、抽取法

所谓抽取法,即我们将每个case语句中的计算内容抽取为一个函数,然后在每个case中只需要进行函数调用即可,如我们将“Avg”这条分之抽取为如下函数:

 void ComputeAvg(const Param& inParam){double dAvg = 0;...    //应用于Avg分之的局部变量...    //实际的计算工作return dAvg;}

然后在FuncToOptimal函数中,每个case分支调用对应的函数即可,那么FuncToOptimal就简化为:

double FuncToOptimal(EDataType eDataTypeToCompute, const Param& inParam)
{double dRetValue = 0;switch (eDataTypeToCompute){case Avg:dRetValue = ComputeAvg(inParam);break;case Min:dRetValue = ComputeMin(inParam);break;case Max:dRetValue = ComputeMax(inParam);break;case TI:dRetValue = ComputeTi(inParam);break;case Tab:dRetValue = ComputeTab(inParam);break;
case ...:...    //其他情况break;default:dRetValue = ComputeDefault(inParam);break;}return dRetValue;
}

这样最明显的好处就是,FuncToOptimal函数不再冗长,它函数的代码行数会大大缩减,使得代码的可读性增强。并且每个case分之的实现都单独在一个接口中实现,这也更能满足一个函数只做一件事的原则。除此之外,这种方法还避免了在函数入口定义所有case分支所需要的局部变量。

尽管如此,但是用这种方法重构之后,FuncToOptimal的函数实现仍然较长,尤其是当EDataType枚举有十几个甚至几十个枚举值的情况。另外,这个函数也违背了一个函数只做一件事的原则(严格来讲,有多少种case分支,它就做了多少件事情)。

3、跳表法

经过抽取法优化之后,虽然函数的可读性增强了,但是函数依然违背了单一原则以及对修改封闭的原则,因为当增加新的枚举值时,我们仍然需要来对函数进行修改。

为继续修改代码,使之满足单一原则和对修改封闭原则,我们可以使用 跳表法,它需要结合之前所讲的抽取法,首先将每个case分支的实现抽取到单独的函数中,然后用一个数组来存储对应的case分支实现的函数地址,这个数组即为跳表。如:

 typedef double (*DataRetriver)(const Param& inParam);    //定义函数指针DataRetirver dataRetriever[20] = {&ComputeAvg, &ComputeMin, ...};    //假设总共有20个分支

定义好跳表之后,对FuncToOptimal的优化如下:

double FuncToOptimal(EDataType eDataToCompute, const Param& inParam){double dTabValue = 0;DataRetriever * pRetriever = dataRetriever[eDataToCompute];if(pRetriever != nullptr)  {dTabValue = pRetriever(inParam);}return dTabValue;}

经过优化之后,FuncToOptimal就同时满足了单一原则和对修改封闭原则。但是跳表法有几个明显的缺点:

1)跳表中,每一个位置的函数指针必须与枚举值严格对应;

2)当枚举值调整之后,跳表必须做相应的调整;

3)对于有些不用实现的case分支,跳表中也必须要为其对应的枚举值留空位

4、继承与多态

所谓继承与多态,即利用C++中的多态性质。优化主要有以下几个步骤:

1)创建一个基类

class DataRetriever  {public:DataRetriver()  = default;~DataRetirver() = default;double RetrieveData(const Param& inParam) = 0;}

2)每一种需要计算的case分支均创建一个类,继承至DataRetriever,如Avg分支:

 class AvgRetriever : public DataRetirever{public:AvgRetriever() = default;~AvgRetriever() = default;double RetrieveData(const Param& inParam);}

3)实现RetrieveData接口,如Avg分支的实现:

 double AvgRetirever::RetrieveData(const Param& inParam){    double dAvg = 0;...    //应用于Avg分之的局部变量...    //实际的计算工作return dAvg;}

4)创建一个工厂方法(当然也可以作为DataRetriever的静态函数方法,此处仅为示例之用),根据不同的枚举值获取相应的对象

 DataRetriever* GetRetriever(EDataType eDataTypeToCompute){switch(eDataTypeToCompute){case Avg:return new AvgRetriever();case ...:return new ...;    //其它情况}return nullptr;}

注:返回值此处直接返回指针,仅仅为示例之用,实际工作中尽量使用智能指针

5)对FuncToOptimal进行优化,优化后函数实现变为:

double FuncToOptimal(EDataType eDataToCompute, const Param& inParam){double dTabValue = 0;DataRetriever * pRetriever = GetRetriever(eDataToCompute);if(pRetriever != nullptr)  {dTabValue = pRetriever->RetrieveData(inParam);delete pRetriever;pRetirever = nullptr;}return dTabValue;}

经过以上5个步骤即可完成对FuncToOptimal函数的重构,重构后函数会缩短到几行。并且代码的结构清晰,可读性强。当任何一个分支的实现改变,或者新增了新的分支,我们都无需对FuncToOptimal进行修改,只需要修改对应分支的类或者为新的分支实现一个类,并在GetRetriever工厂方法中增加对新的分支的处理即可。

5、总结

在本为中,我们针对switch...case语句的优化给出了以下3中解决方法:

1)抽取法

这种方法最为简单,它能够有效的减少代码行数,并增强代码可读性,降低维护成本。但这种方法后,函数仍然不符合单一原则以及对修改封闭原则,并且函数对修改不封闭。

2)跳表法

这种方法是在抽取法之上对函数进行进一步的优化。它能够使得优化后的代码满足单一原则和对修改封闭原则,但它不够灵活。

3)继承与多态

这种方法是三种方法中,对函数优化最为彻底的方法,对于复杂的switch...case语句,我们通常建议使用这种方法。

最后,并不是所有的switch...case语句都需要优化,只有case分支较多,且分支中处理比较复杂的情况才需要进行优化。在优化的时候,也不是一定要选择继承与多态的方法,这需要根据具体情况而定。

P.S. 以上为个人意见,如有纰漏,请您不吝赐教。

冗长switch-case语句优化方案相关推荐

  1. 如何解决大量的if语句或switch case语句?

    洪流学堂,让你快人几步. 本篇内容来自洪流读书会解读书籍<代码大全2>. 很多面试官喜欢问这样的问题,如何解决大量的if语句或switch case语句?如果你仅仅在优化的层面回答,可能不 ...

  2. C语言case次数有限制吗,用switch...case语句统计数字、空格和其他字符出现的次数...

    //用switch...case语句统计数字.空格和其他字符出现的次数 //转自K&R #include int main(void) { int c, i, nwhite, nother, ...

  3. 在C++中对字符串std::string使用switch/case语句

    如果你使用C语音的string,也就是char *,是可以放在switch/case语句中的. 在C++中是不能对字符串string使用switch/case语句的,这里的string指的是std:: ...

  4. 在switch case 语句中能否使用continue 关键字?为什么?

    在switch case 语句中能否使用continue 关键字?为什么? #include <stdio.h>   int main()   {       int a;       p ...

  5. python中有没有switch_Python为什么没有switch/case语句?

    与我之前使用的所有语言都不同,Python没有switch/case语句.为了达到这种分支语句的效果,一般方法是使用字典映射: def numbers_to_strings(argument): sw ...

  6. java break在switch_java中switch case语句需要加入break的原因解析

    java中switch case语句需要加入break的原因解析 java 中使用switch case语句需要加入break 做了具体的实例分析,及编译源码,在源码中分析应该如何使用,大家可以参考下 ...

  7. python中case的用法_用 Python 实现简单的 switch/case 语句

    在Python中是没有Switch / Case语句的,很多人认为这种语句不够优雅灵活,在Python中用字典来处理多条件匹配问题字典会更简单高效,对于有一定经验的Python玩家不得不承认,的确如此 ...

  8. python中没有switch-case_Python为什么没有switch/case语句?

    与我之前使用的所有语言都不同,Python没有switch/case语句.为了达到这种分支语句的效果,一般方法是使用字典映射: def numbers_to_strings(argument): sw ...

  9. c语言 case语句用法,switch ... case语句的用法[组图]

    switch ... case语句的用法[组图] 08-13栏目:技术 TAG:switch case语句 switch case语句 当情况大于或等于4种的时候就用switch ...  case语 ...

最新文章

  1. 【Groovy】构建工具 ( 构建工具引入 | Gradle 构建工具作用 | 传统的依赖管理 )
  2. GC垃圾回收的三色标记算法
  3. tornado post第3方_[33]python-Web-框架-Tornado
  4. 接口测试工具-fiddler的运用
  5. ❤️六W字《计算机基础知识》(七)(建议收藏)❤️
  6. IOS多线程任务(综述篇)
  7. 事件 ID 1505,1508
  8. 生活大爆炸系列之磨望远镜
  9. [转载] 面试常见问题总结
  10. Flac3d v3.00.251
  11. Android PackageInstaller 静默安装的实现(附源码)
  12. cuda环境安装--windows离线安装
  13. ps蒙版上渐变工具的使用及抠图方法
  14. 优化 WordPress 网站让百度快速收录
  15. 【编程题】【Scratch四级】2021.09 小猫钓鱼
  16. 华为云对象存储服务OBS教你一招轻松解决存储难题
  17. 软件开发、系统定制、小程序等怎么报价?
  18. 培训班和科班出来的程序员有什么不同之处?
  19. 微信小程序调用python分析图片_小帅丶干货之图像识别在微信小程序展示
  20. v880+ 联通定制手机的永久ROOT和精简版本 国行

热门文章

  1. 2021-05 ISCC竞赛
  2. Shopify独立站中东市场怎么做
  3. 坏账损失及应纳税影响会计处理
  4. html 自定义nav,HTML——nav
  5. mysql创建数据库CREATE DATABASE
  6. 瑞萨 RH850 FCL、FDL 和 EEL 库的配置和使用
  7. 计算机毕业设计、课程设计、实战项目之[含论文+源码等]基于SpringBoot在线电影订票系统[包运行成功]
  8. 2022最新自动化测试面试题精选
  9. 谈谈Enter回车键提交表单那些事
  10. 云计算机房概念股,云计算数据中心概念股龙头有哪些?2020云计算数据中心受益股一览表(2)...