Qt篇: if else替代方案 -- 表驱动法
为什么要替换if else语句
有些人可能会认为if...else挺好的,这样写也简单,又容易看得懂,是这样没错。但对于写应用层软件来说,使用if...else的场景很多,有时候需要大量的if...else语句,这样的代码会导致代码变得冗长且难以阅读,对于后期维护也是比较容易出错的。那么该用什么方案来替代呢?
- switch:换汤不换药,使用switch同样会使得代码变得冗长,唯一的好处是比if...else更加容易阅读;
- 多维数组:这个确实一个不错的方案,但是有个缺点是容易控制不好数组边界,容易在访问时越界引发崩溃;
- 策略模式和多态模式:这个方案也确实可行,但是为了优化if...else增加了非常复杂的逻辑,且还需要创建基类和多个派生类,总的来说不值得。
经过多种方案测试,表驱动法才是最优解。
表驱动法
表驱动法通过使用查找表(通常是数组、map字典、哈希表或其他数据结构)来存储条件和相应的操作,可以简化代码结构,提高代码的可维护性。 它核心思想是将条件和操作分离,将条件判断替换为查找表的查询操作。查找表通常包含一个映射,将输入(条件)映射到对应的输出(操作)。
优点
- 可扩展性:添加新的条件和操作非常简单,只需要在表中添加一个新的映射,而不需要修改大量的if...else语句;
- 可维护性:表驱动法使得代码结构更加清晰,更加整洁,更加通俗易懂,逻辑和数据分离,易于维护;
- 性能优化:在表中查找操作通常比多次条件判断要高效,特别是在有大量条件时。
案例模拟
假设有一个场景,需要根据输入的状态取代if的条件(state)来执行相应的操作。你可以创建一个包含所有可能状态及其对应操作的表。
无参场景
该场景是操作处理函数上无需传递任何参数,下方代码handleStateA、handleStateB和handleStateC都是不需要传递任何参数的。
#include <iostream>
#include <map>
#include <functional>
// 定义每个状态对应的操作
void handleStateA() {
std::cout << "Handling State A" << std::endl;
}
void handleStateB() {
std::cout << "Handling State B" << std::endl;
}
void handleStateC() {
std::cout << "Handling State C" << std::endl;
}
// 封装查表和执行操作的函数
void executeHandler(const std::string& state) {
// 构建操作表
static const std::map<std::string, std::function<void()>> handlers = {
{"stateA", handleStateA},
{"stateB", handleStateB},
{"stateC", handleStateC}
};
// 查表并执行对应的操作
auto it = handlers.find(state);
if (it != handlers.end()) {
it->second();
} else {
std::cout << "Invalid State" << std::endl;
}
}
int main() {
// 模拟输入状态
std::string currentState = "stateB";
// 直接调用封装的函数
executeHandler(currentState); // 输出:Handling State B
return 0;
}
这段代码好处很多:
- 静态操作表:在executeHandler函数中,handlers映射表被定义为static,这意味着这个表只会在第一次调用时初始化一次,后续的调用将不会重复创建该表,提升了性能;
- 参数传递:调用executeHandler时,只需要传入当前的状态(例如"stateB"),函数内部会根据这个状态查找并执行对应的操作;
- 错误处理:如果传入的状态不在表中,函数会输出“Invalid State”提示。你可以根据需要修改这个逻辑,例如抛出异常或执行默认操作。
有参场景
由于可能每个操作处理函数的参数类型都是不一样的,我们也同样可完美解决,可以使用std::variant或者QVariant来实现,QVariant相信大家都很熟练了,也为了该方案可以证明在纯C++环境下实现,所以我下面代码使用std::variant来实现,std::variant是C++17引入的一个类型安全的联合体,可以包含不同类型的数据,千万别忘了在pro文件引入C++17。
#include <iostream>
#include <map>
#include <functional>
#include <variant>
// 定义不同的处理函数
void handleStateA(int value) {
std::cout << "Handling State A with int value: " << value << std::endl;
}
void handleStateB(double value) {
std::cout << "Handling State B with double value: " << value << std::endl;
}
void handleStateC(const std::string& value) {
std::cout << "Handling State C with string value: " << value << std::endl;
}
// 定义支持不同类型参数的std::variant
using VariantType = std::variant<int, double, std::string>;
void executeHandler(const std::string& state, const VariantType& value) {
// 构建操作表,std::function<void(VariantType)>表示每个处理函数接受VariantType参数
static const std::map<std::string, std::function<void(const VariantType&)>> handlers = {
{"stateA", [](const VariantType& v) { handleStateA(std::get<int>(v)); }},
{"stateB", [](const VariantType& v) { handleStateB(std::get<double>(v)); }},
{"stateC", [](const VariantType& v) { handleStateC(std::get<std::string>(v)); }}
};
// 查表并执行对应的操作,传入VariantType
auto it = handlers.find(state);
if (it != handlers.end()) {
it->second(value); // 调用并传递参数
} else {
std::cout << "Invalid State" << std::endl;
}
}
int main() {
// 模拟输入状态和参数
std::string currentState = "stateA";
VariantType value = 42;
// 直接调用封装的函数,并传入参数
executeHandler(currentState, value);
// 测试其他状态
executeHandler("stateB", 3.14);
executeHandler("stateC", std::string("Hello World"));
return 0;
}
总结
使用表驱动方案替代if...else语句,不仅能使代码更加清晰、简洁和高效,还能提升代码的可维护性和灵活性,更符合现代C++编程的最佳实践,特别是在处理复杂逻辑和多种条件时。