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++编程的最佳实践,特别是在处理复杂逻辑和多种条件时。