如何减少或者改写代码中的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。它是一种常用的解决状态扭转问题的方法,通过定义状态以及状态之间的转移规则来控制状态的流转。
要理解有限状态机,首先我们需要理解整个模型。我们以灯的开关为例,先看图:
如上图所示,可以提取出几个关键点:
1.状态
2.动作
3.状态过渡
4.转换事件
5.FSM
一般状态都会对应一个动作,所以我们把状态和动作放在状态里面,然后状态过渡和过渡事件放在状态过渡里面,最后由状态机驱动。于是一个状态机模型就有了,状态、状态过渡、状态机。
其中我们最容易忽略的一点就是状态过渡,这是个最不容易想到和抽象出来的一个对象,为什么要把状态过渡抽象出来呢?因为状态过渡就是每个状态之间相互转换的条件,这个抽象出来后,以后就可以解耦每个状态之间的转换条件,少些许多分支判断且更利用维护
网上下过单都知道,一个订单从创建开始要经历好几个状态,中间也有不同的操作可以进行,下面是一个比较典型的流程设计,经过一定简化,并以“状态”的主视角来描绘:
图中圆边的矩形代表状态,最上面一排是“正常”的状态和流程;第二排的菱形则表示一些“逆向”子流程,通常是由用户或客户发起的特殊操作,这些操作会带来其他一些订单状态,为了简单起见没有在这里展开。
流程说明:
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())
err := order.Event(context.Background(), "pay") if err != nil { fmt.Println(err) }
fmt.Println(order.Current())
err = order.Event(context.Background(), "deliver") if err != nil { fmt.Println(err) }
fmt.Println(order.Current())
}
|
创建有限状态机对象:
使用 fsm.NewFSM
创建了一个有限状态机对象,指定了初始状态、事件以及状态转换关系。
定义事件和状态转换关系:
在 fsm.Events
部分,定义了不同事件(例如 “pay”、”deliver”)以及它们可以触发的状态转换关系。
定义事件触发的回调函数:
使用 fsm.Callbacks
部分定义了不同事件触发时执行的回调函数。这些回调可以在不同事件的不同阶段执行,以处理相关逻辑。
使用有限状态机执行事件:
使用 order.Event
方法来触发状态机中定义的事件。如果事件执行成功,err
将为 nil
;否则,它将包含一个错误信息。
获取当前状态:
使用 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;
struct created {}; struct paid {}; struct delivering {}; struct received {}; struct done {};
struct pay {}; struct deliver {}; struct receive {}; struct confirm {};
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;
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; }
|
- 首先,我们定义了一组状态(created, paid, delivering, received, done)和一组事件(pay, deliver, receive, confirm)。
- 然后,我们创建一个名为
order_process
的状态机,它使用 SML 的 make_transition_table
来定义状态转换。每个状态转换规则表示了从一个状态到另一个状态的事件触发条件。
- 在
main
函数中,我们创建了状态机对象 sm
,并使用 process_event
方法触发不同事件。随着事件的触发,状态机会根据定义的状态转换规则自动切换状态。
- 最后,我们使用
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;
struct created {}; struct paid {}; struct delivering {}; struct received {}; struct done {};
struct pay {}; struct deliver {}; struct receive {}; struct confirm {};
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; }
|