LOADING

加载过慢请开启缓存 浏览器默认开启

2024/9/6

如何减少或者改写代码中的if

方法一:

映射表(Map/Dictionary):使用映射表来替代if语句。将条件和相应的操作映射到一个关联容器中,然后根据条件查找并执行相应的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <map>
#include <functional>

void Action1() { std::cout << "Action 1" << std::endl; }
void Action2() { std::cout << "Action 2" << std::endl; }
void Action3() { std::cout << "Action 3" << std::endl; }

int main() {
std::map<int, std::function<void()>> actionMap;
actionMap[1] = Action1;
actionMap[2] = Action2;
actionMap[3] = Action3;

int condition = 2;
if (actionMap.find(condition) != actionMap.end()) {
actionMap[condition]();
} else {
std::cout << "Condition not found" << std::endl;
}

return 0;
}

方法二:

表驱动编程(Table-Driven Programming):将条件和操作的关联数据结构化存储,然后使用查找表来执行操作,而不是使用if语句。

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
#include <iostream>

// 定义一个结构体,用于存储条件和相应的操作
struct ConditionAction {
int condition;
void (*action)();
};

// 定义操作函数
void Action1() { std::cout << "Action 1" << std::endl; }
void Action2() { std::cout << "Action 2" << std::endl; }
void Action3() { std::cout << "Action 3" << std::endl; }

int main() {
// 创建一个表驱动数组,存储条件和相应的操作
ConditionAction conditionActions[] = {
{1, Action1},
{2, Action2},
{3, Action3}
};

int condition = 2; // 设置条件
// 遍历数组,找到匹配的条件并执行相应的操作
for (const auto& ca : conditionActions) {
if (ca.condition == condition) {
ca.action();
return 0; // 找到匹配条件后可以退出循环
}
}

std::cout << "Condition not found" << std::endl;
return 0;
}

方法三:

什么是有限状态机?

有限状态机(finite state machine,简称 FSM),有时也被称为 finite state automation,有时就简单地叫 state machine。它是一种常用的解决状态扭转问题的方法,通过定义状态以及状态之间的转移规则来控制状态的流转。

要理解有限状态机,首先我们需要理解整个模型。我们以灯的开关为例,先看图:

image-20231106131539031

如上图所示,可以提取出几个关键点:

1.状态

2.动作

3.状态过渡

4.转换事件

5.FSM

一般状态都会对应一个动作,所以我们把状态和动作放在状态里面,然后状态过渡和过渡事件放在状态过渡里面,最后由状态机驱动。于是一个状态机模型就有了,状态、状态过渡、状态机。

其中我们最容易忽略的一点就是状态过渡,这是个最不容易想到和抽象出来的一个对象,为什么要把状态过渡抽象出来呢?因为状态过渡就是每个状态之间相互转换的条件,这个抽象出来后,以后就可以解耦每个状态之间的转换条件,少些许多分支判断且更利用维护

网上下过单都知道,一个订单从创建开始要经历好几个状态,中间也有不同的操作可以进行,下面是一个比较典型的流程设计,经过一定简化,并以“状态”的主视角来描绘:

image-20231106131700846

图中圆边的矩形代表状态,最上面一排是“正常”的状态和流程;第二排的菱形则表示一些“逆向”子流程,通常是由用户或客户发起的特殊操作,这些操作会带来其他一些订单状态,为了简单起见没有在这里展开。

流程说明:

1.当买家点击下单时订单生成,处于“已创建”状态;这个状态下其他可选操作包括“修改”、“取消”等,分别会去到订单修改和订单取消子流程(略);

2.这个状态下的正常操作是“支付”,如果输入“支付成功”会进入下一个状态“已支付”,“支付失败”或者没有任何操作则停在本状态;

支付成功后进入处于“已支付”状态;这个状态下需要等待商家发货,商家输入“已发货”会进入下一个状态“配送中”;

3.这个状态下不能修改订单了,但仍然可以取消订单;商家发货后进入“配送中”状态;

当配送到货,买家签收成功输入则进入下一个状态“已签收”;如果配送失败(买家不在家之类的情况)则留在“配送中”状态(另外择时重新送货);

这个状态下已不能修改和取消订单,但是可以发起退货申请,进入退货子流程(略);

4.买家签收后进入“已签收”状态;

5.买家满意,确认订单完成则进入最后状态“已完成”,订单生命周期结束;

否则买家可以发起退货进入退货子流程(略)。

从这里我们可以看到,实际业务系统中状态和转换的规则相当复杂(我们这还是大大简化的版本),每个状态下允许的操作和可能转换的下一个状态都是严格受控的,现在我们思考一下,我们可以如何用程序来实现这样的流程呢?

利用有限状态机编写易于维护的代码

回忆我们之前提到的,流程和行为控制的关键是管理:

在某个状态下什么能做什么不能做;

做了什么会变成另外的什么状态。

最简单直接的办法就是书写一堆 if…else 的判断规则,大致会是这个样子:

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
package main

import "errors"

type OrderState uint8

const (
Created OrderState = 1 << iota
Paid
Delivering
Received
Done
Cancelling
Returning
Closed
)

type order struct {
state OrderState
}

func NewOrder() *order {
return &order{
state: Created,
}
}

func (o *order) CanPay() bool {
return o.state == Created
}

func (o *order) CanDeliver() bool {
return o.state == Paid
}

func (o *order) CanCancel() bool {
return o.state == Created || o.state == Paid
}

func (o *order) CanReceive() bool {
return o.state == Delivering
}

func (o *order) PaymentService() bool {
//调用远程接口完成实际支付

return false
}

// 然后是关键操作的实现,比如支付
func (o *order) Pay() (bool, error) {
if o.CanPay() {
if ok := o.PaymentService(); ok {
o.state = Paid
return true, nil
}

} else {
//抛出错误
return false, errors.New("Pay Error")
}

return false, nil
}

// 然后是关键操作的实现,比如支付
func (o *order) Cancel() (bool, error) {
if o.CanCancel() {
// 取消订单
o.state = Cancelling

// 取消订单,申请审批和清理数据,如果顺利成功再关闭订单
o.state = Closed
} else {
// 抛出错误
return false, errors.New("Cancel Error")
}

return false, nil
}

这样的代码非常冗长和重复,难以维护且难以修改,设想一下,假设在上面的基础上再增加一个状态,要连带修改不确定几处地方,做完这样的修改还需要相应修改所有的测试用例,累就不说了,关键是容易出错。

有限状态机实际上是这些“八股”的通用实现,然后提供一个非常简洁的接口供我们使用。有兴趣的话可以自己尝试用 Golang 写一个 FSM 的实现出来,只做最基本功能的话也不是很难,但我们实际上没必要自己写, Golang也有不少FSM的第三方实现,比如 github.com/looplab/fsm 这个库,我们就可以用它来展示一下上面的流程如何用 FSM 来实现:

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
package main

import (
"context"
"fmt"
"github.com/looplab/fsm"
)

func main() {
// 创建有限状态机对象
order := fsm.NewFSM(
"created", // 初始状态
fsm.Events{
{Name: "pay", Src: []string{"created"}, Dst: "paid"},
{Name: "deliver", Src: []string{"paid"}, Dst: "delivering"},
{Name: "receive", Src: []string{"delivering"}, Dst: "received"},
{Name: "confirm", Src: []string{"received"}, Dst: "done"},
},
fsm.Callbacks{
"before_pay": func(e *fsm.Event) {
fmt.Println("支付服务申请中……")
// 可以在此处执行相关操作
},
"paid": func(e *fsm.Event) {
fmt.Println("支付成功")
},
"after_deliver": func(e *fsm.Event) {
fmt.Println("已通知用户:商品配送中")
},
},
)

// 获取当前状态
fmt.Println(order.Current())

// 触发 "pay" 事件
err := order.Event(context.Background(), "pay")
if err != nil {
fmt.Println(err)
}

// 获取当前状态
fmt.Println(order.Current())

// 触发 "deliver" 事件
err = order.Event(context.Background(), "deliver")
if err != nil {
fmt.Println(err)
}

// 获取当前状态
fmt.Println(order.Current())

// 可以继续触发其他事件
}

  1. 创建有限状态机对象:

    使用 fsm.NewFSM 创建了一个有限状态机对象,指定了初始状态、事件以及状态转换关系。

  2. 定义事件和状态转换关系:

    fsm.Events 部分,定义了不同事件(例如 “pay”、”deliver”)以及它们可以触发的状态转换关系。

  3. 定义事件触发的回调函数:

    使用 fsm.Callbacks 部分定义了不同事件触发时执行的回调函数。这些回调可以在不同事件的不同阶段执行,以处理相关逻辑。

  4. 使用有限状态机执行事件:

    使用 order.Event 方法来触发状态机中定义的事件。如果事件执行成功,err 将为 nil;否则,它将包含一个错误信息。

  5. 获取当前状态:

    使用 order.Current 方法来获取当前状态。

deliver 事件

在有限状态机的定义中,您可以看到以下部分:

1
2
3
4
5
fsm.Events{
// ...
{Name: "deliver", Src: []string{"paid"}, Dst: "delivering"},
// ...
}

这部分定义了一个名为 “deliver” 的事件,它的源状态(Src)是 “paid”,目标状态(Dst)是 “delivering”。这意味着在当前状态为 “paid” 时,触发 “deliver” 事件将导致状态转换到 “delivering”。

after_deliver 回调

在回调部分,定义了一个名为 “after_deliver” 的回调函数,如下:

1
2
3
4
5
6
7
8
fsm.Callbacks{
// ...
"after_deliver": func(e *fsm.Event) {
fmt.Println("已通知用户:商品配送中")
},
// ...
}

这个回调函数会在 “deliver” 事件触发之后执行,因为它的名称是 “after_deliver”。所以,当 “deliver” 事件导致状态从 “paid” 转换到 “delivering” 时,"after_deliver" 回调函数将被执行,输出 “已通知用户:商品配送中”。

总结一下,事件和回调之间的关联是通过事件的名称和回调的名称建立的。在状态机中定义事件和回调后,事件触发时,与之关联的回调函数将被执行,以执行相应的操作。在这个示例中,”deliver” 事件触发后,”after_deliver” 回调函数会输出一条消息。

github.com/looplab/fsm 包中,回调函数的命名是有含义的。具体来说,回调函数的名称中包含 before_after_ 前缀,以表示回调函数在状态机事件的执行顺序中的时机。

  • before_ 前缀:回调函数带有 before_ 前缀的名称会在执行与之关联的事件之前被调用。这些回调函数可以用于在事件执行前执行一些前期准备工作,或者可以用于在事件执行之前进行一些验证或条件检查。
  • after_ 前缀:回调函数带有 after_ 前缀的名称会在执行与之关联的事件之后被调用。这些回调函数可以用于在事件执行后进行一些清理工作或其他操作,也可以用于处理事件执行后的后续逻辑。

这种命名约定使得在状态机的事件处理过程中,可以很清晰地识别哪些回调函数是在事件执行前调用的,哪些是在事件执行后调用的。这有助于更好地控制事件处理的流程,同时使代码更易于理解和维护。

例如,在代码示例中,定义的回调函数中有 “before_pay” 和 “after_deliver” 回调函数。 “before_pay” 会在 “pay” 事件执行前被调用,而 “after_deliver” 会在 “deliver” 事件执行后被调用,从而帮助控制事件的前后逻辑。

github.com/looplab/fsm 包中,状态转换后的新状态(目标状态)可以与回调函数同名,以使状态机在进入新状态时执行特定的回调函数。这是一种约定,用于执行与状态同名的回调函数。

在您的示例中,有一个状态转换将状态从 “created” 转换为 “paid”,并且在 “paid” 状态下,有一个同名的回调函数 “paid”。这意味着当状态机执行从 “created” 到 “paid” 的状态转换时,状态机会自动查找并执行名为 “paid” 的回调函数。

具体来说,在状态机执行以下代码时:

1
err := order.Event(context.Background(), "pay")

状态机会检查当前状态是否为 “created” 并且事件 “pay” 是否允许从 “created” 转换到 “paid”,如果条件满足,状态机会执行状态转换,并在状态变为 “paid” 后执行同名的回调函数 “paid”。

这种方式可以用于在状态转换的过程中执行特定的逻辑。同名的回调函数的参数通常是 *fsm.Event 对象,它可以提供关于状态机事件的信息以及一些控制功能。例如,在 “paid” 回调中,您可以执行与订单支付相关的操作

关于cpp:GitHub:https://github.com/boost-experimental/sml

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
#include <iostream>
#include <sml.hpp>

namespace sml = boost::sml;

// Define states
struct created {};
struct paid {};
struct delivering {};
struct received {};
struct done {};

// Define events
struct pay {};
struct deliver {};
struct receive {};
struct confirm {};

/*
*state<created> + event<pay> = state<paid>:这一行表示了一个状态转换规则。它的含义是,在状态为 created 的情况下,如果触发了 pay 事件,那么状态将从 created 转换到 paid。
state<paid> + event<deliver> = state<delivering>:这行表示了另一个状态转换规则。在状态为 paid 的情况下,如果触发了 deliver 事件,状态将从 paid 转换到 delivering。
*/

// Define a state machine
struct order_process {
auto operator()() const {
using namespace sml;

return make_transition_table(
*state<created> + event<pay> = state<paid>,
state<paid> + event<deliver> = state<delivering>,
state<delivering> + event<receive> = state<received>,
state<received> + event<confirm> = state<done>
);
}
};

int main() {
sml::sm<order_process> sm;

std::cout << "Current state: " << sm.current_state().c_str() << std::endl;

// Trigger events
sm.process_event(pay{});
std::cout << "Current state: " << sm.current_state().c_str() << std::endl;

sm.process_event(deliver{});
std::cout << "Current state: " << sm.current_state().c_str() << std::endl;

sm.process_event(receive{});
std::cout << "Current state: " << sm.current_state().c_str() << std::endl;

sm.process_event(confirm{});
std::cout << "Current state: " << sm.current_state().c_str() << std::endl;

return 0;
}

  1. 首先,我们定义了一组状态(created, paid, delivering, received, done)和一组事件(pay, deliver, receive, confirm)。
  2. 然后,我们创建一个名为 order_process 的状态机,它使用 SML 的 make_transition_table 来定义状态转换。每个状态转换规则表示了从一个状态到另一个状态的事件触发条件。
  3. main 函数中,我们创建了状态机对象 sm,并使用 process_event 方法触发不同事件。随着事件的触发,状态机会根据定义的状态转换规则自动切换状态。
  4. 最后,我们使用 sm.current_state() 来获取当前状态并打印出来。
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
#include <iostream>
#include <sml.hpp>

namespace sml = boost::sml;

// Define states
struct created {};
struct paid {};
struct delivering {};
struct received {};
struct done {};

// Define events
struct pay {};
struct deliver {};
struct receive {};
struct confirm {};

// Define a state machine
struct order_process {
auto operator()() const {
using namespace sml;

return make_transition_table(
*state<created> + event<pay> = state<paid>,
state<paid> + event<deliver> = state<delivering>,
state<delivering> + event<receive> = state<received>,
state<received> + event<confirm> = state<done>,

on_entry<delivering> / [](const auto&) {
std::cout << "Entering 'delivering' state" << std::endl;
},

on_exit<delivering> / [] {
std::cout << "Exiting 'delivering' state" << std::endl;
},

on<pay> / [](const auto&) {
std::cout << "Payment callback: Payment received" << std::endl;
},

on<receive> / [](const auto&) {
std::cout << "Receive callback: Order received" << std::endl;
}
);
}
};

int main() {
sml::sm<order_process> sm;

sm.process_event(pay{});
sm.process_event(deliver{});
sm.process_event(receive{});
sm.process_event(confirm{});

return 0;
}