代码中的if…else杂谈3

if-else链过长的几个解决方法。

map映射

老代码中经常见到下面这种写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void MainWindow::showInfo(const QString& _animal)
{
if (_animal.isEmpty() == true)
{
return;
}

if (_animal == u8"狗")
{
//各种复制粘贴的代码
ui->label->setText(u8"狗有4条腿");
}
else if (_animal == u8"海鸥")
{
//各种复制粘贴的代码
ui->label->setText(u8"海鸥有2条腿");
}
else if (_animal == u8"螃蟹")
{
//各种复制粘贴的代码
ui->label->setText(u8"螃蟹有8条腿");
}
}
  1. 这么写代码通常伴随着大量的复制粘贴,如果代码中不存在大量重复代码,这么写也可以接受。
  2. 如果后期经常需要增加else-if,这么写多少有点不妥。

解决

上述问题可以通过map映射的方式解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
enum Animals
{
DOG,
CRAB,//螃蟹
SEA_MEW //海鸥
};

struct AnimalInfo
{
AnimalInfo(QString _name, int _legs) :
name(_name),
legs(_legs) {}

QString name;
int legs;
};

class MainWindow : public QMainWindow
{
Q_OBJECT

public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void showInfo(const QString& _animal);
void showInfo2(Animals _animal);

private:
const std::map<Animals, AnimalInfo> ANIMAL_MAP =
{
std::pair<Animals, AnimalInfo>(DOG, AnimalInfo(u8"狗", 4)),
std::pair<Animals, AnimalInfo>(CRAB, AnimalInfo(u8"螃蟹", 8)),
std::pair<Animals, AnimalInfo>(SEA_MEW, AnimalInfo(u8"海鸥", 2)),
};
};
1
2
3
4
5
6
7
8
9
10
11
void MainWindow::showInfo(Animals _animal)
{
auto it = ANIMAL_MAP.find(_animal);
if (ANIMAL_MAP.end() == it)
return;

QString animalsInfoText = QString(u8"%1有%2条腿")
.arg(it->second.name).arg(it->second.legs);

ui->label->setText(animalsInfoText);
}
  1. 所有逻辑在最开始就已经写好,后期增加可能只需要修改ANIMAL_MAP,比使用if-else更加一目了然。
  2. showInfo的参数由字符串改为枚举,更方便在编译器发现问题。

界面状态的复杂逻辑

可以借助QStackedWidgetQButtonGroupstd::vectorstd::map配合来处理一些界面上的复杂逻辑。

如上图所示,切换第一行的按钮,会改变第二行按钮的可点击状态,点第二个按钮会执行相关操作:

mainwindow.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <QDialog>
#include <QButtonGroup>
#include <set>

enum class Animal
{
SEA_MEW, //海鸥
DOG, //小狗
CRAB //螃蟹
};

enum class Skill
{
SWIM,//游
FLY, //飞
RUN, //跑
};

using SkillSet = std::set<Skill>;

struct AnimalInfo
{
AnimalInfo(Animal _animal, QString _name, SkillSet _skills)
: animal(_animal)
, skills(_skills)
, name(_name){}

Animal animal;
QString name;
SkillSet skills;
};

class MainWindow : public QDialog
{
Q_OBJECT

public:
explicit MainWindow(QWidget *parent = nullptr);

signals:
void toDo(Animal, Skill);

private:
void initUI();
void setConnects();

private:
QButtonGroup* animalButtonGroup_ = nullptr;
QButtonGroup* skillButtonGroup_ = nullptr;

const std::vector<std::pair<Skill, QString>>SKILL_VECTOR =
{
std::pair<Skill, QString>(Skill::SWIM, u8"游"),
std::pair<Skill, QString>(Skill::FLY, u8"飞"),
std::pair<Skill, QString>(Skill::RUN, u8"跑"),
};

const std::vector<AnimalInfo> ANIMAL_INFO_VECTOR =
{
AnimalInfo(Animal::SEA_MEW, u8"海鸥", {
Skill::SWIM,
Skill::FLY,
Skill::RUN,
}),

AnimalInfo(Animal::DOG, u8"小狗",{
Skill::RUN,
}),

AnimalInfo(Animal::CRAB, u8"螃蟹",{
Skill::SWIM,
Skill::RUN,
}),
};
};

mainwindow.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include "mainwindow.h"
#include <QHBoxLayout>
#include <QPushButton>
#include <QMessageBox>

MainWindow::MainWindow(QWidget *parent) :
QDialog(parent)
{
initUI();
setConnects();
animalButtonGroup_->button(0)->click();
}

void MainWindow::initUI()
{
animalButtonGroup_ = new QButtonGroup(this);
QHBoxLayout* animalHLayout = new QHBoxLayout;
animalHLayout->setContentsMargins(0, 0, 0, 0);
animalHLayout->setSpacing(10);

for (auto it : ANIMAL_INFO_VECTOR)
{
auto button = new QPushButton(it.name, this);
button->setCheckable(true);
animalHLayout->addWidget(button);
animalButtonGroup_->addButton(button, animalButtonGroup_->buttons().size());
}

skillButtonGroup_ = new QButtonGroup(this);
QHBoxLayout* skillHLayout = new QHBoxLayout;
skillHLayout->setContentsMargins(0, 0, 0, 0);
skillHLayout->setSpacing(10);

for (auto it : SKILL_VECTOR)
{
auto button = new QPushButton(it.second, this);
skillHLayout->addWidget(button);
skillButtonGroup_->addButton(button, skillButtonGroup_->buttons().size());
}

QVBoxLayout* mainVLayout = new QVBoxLayout;
mainVLayout->setContentsMargins(0, 0, 0, 0);
mainVLayout->setSpacing(20);

mainVLayout->addLayout(animalHLayout);
mainVLayout->addLayout(skillHLayout);
mainVLayout->addStretch(1);

this->setLayout(mainVLayout);
}

void MainWindow::setConnects()
{
connect(animalButtonGroup_, static_cast<void(QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked),
[=](int _id) {
if (_id < 0 || _id >= ANIMAL_INFO_VECTOR.size())
return;

auto skills = ANIMAL_INFO_VECTOR[_id].skills;
for (size_t i = 0; i < SKILL_VECTOR.size(); i++)
{
auto skill = SKILL_VECTOR[i].first;
auto it = skills.find(skill);
if (it != skills.end())
skillButtonGroup_->button(i)->setEnabled(true);
else
skillButtonGroup_->button(i)->setEnabled(false);
}
});

connect(skillButtonGroup_, static_cast<void(QButtonGroup::*)(QAbstractButton *)>(&QButtonGroup::buttonClicked),
[=](QAbstractButton *button) {
if (button == nullptr)
return;

auto animalID = animalButtonGroup_->checkedId();
QString animalName = ANIMAL_INFO_VECTOR[animalID].name;
QString contentToShow = QString(u8"%1会%2").arg(animalName).arg(button->text());
QMessageBox::information(this, u8"提示", contentToShow);
});

//connect(skillButtonGroup_, static_cast<void(QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked),
// [=](int _id) {
// if (_id < 0 || _id >= SKILL_VECTOR.size())
// return;

// auto animalID = animalButtonGroup_->checkedId();
// auto animal = ANIMAL_INFO_VECTOR[animalID].animal;
//
// emit toDo(animal, SKILL_VECTOR[_id].first);
//});
}

运行结果如图所示:

  1. 配合animalButtonGroup_skillButtonGroup_两个QButtonGroup,界面从创建到交互所需信息都包含到了SKILL_VECTORANIMAL_INFO_VECTOR中。
  2. 第二行按钮的置灰逻辑只有12行代码,逻辑清晰且不受按钮个数增加的影响。
  3. 注释部分代码更像是日常使用(其上方代码只是为了演示),配合Qt反射实现的简单工厂模式(参见简单工厂模式与Qt类反射2)能使代码更加符合开-闭原则。

职责链

职责链模式有时也能减少该类if-else的出现。

多态、工厂模式

多态、工厂模式也是解决该类问题的有效方法,具体可参见简单工厂模式与Qt类反射2