最近学习四大行为模式, 跟着网上照葫芦画瓢,其中过程挺有意思的。
练习目标:
一个上锁的密码门,密码输错三次则锁定5秒,强行输入则状态锁死,输入正确密码则打开门
%%%-------------------------------------------------------------------
%%% @author
%%% @copyright (C) 2020
%%% @doc
%%%
%%% @end
%%% Created : 2021/3/23-16-57
%%%-------------------------------------------------------------------
-module(fsm_test).
-description("fsm_test").
-behaviour(gen_fsm).
-vsn(1).
%% API
-export([init/1, start_link/1, button/1, close/2, open/2, lock/2, handle_event/3, handle_sync_event/4, handle_info/3, terminate/3,stop/0]).
start_link(Pwd) ->
%% {local,zy_test_fsm} 本地状态机名称
%% 第二参是回调模块名
%% 第三参Pwd是init的回调参数 注册成功则 M:init(Pwd) -> {ok,StateName,StateData}
%% 第四参是状态机选项
gen_fsm:start_link({local, zy_test_fsm}, ?MODULE, Pwd, []).
%% gen_fsm:start_link 会通过监控树加入
%% gen_fsm:start 则是启动单独的gen_fsm进场
init(Pwd) ->
%% {ok,StateName,StateData}
%% StateName则为 初始状态
%% StateData则为 内部状态
%% {[],Pwd} 业务逻辑数据,[]=输入的密码,Pwd=正确密码
process_flag(trap_exit,true),
{ok, close, {[], Pwd}}.
button(InputPwd) ->
%% 使用事件通知状态机状态改变
%% zy_test_fsm 即状态机名称
%% {button, InputPwd} 即消息
%% 当zy_test_fsm状态机收到消息时会调用StateName(Event,StateData) -> {next_state,StateName1,StateData1}
%% 因为初始状态是close,所以首次向zy_test_fsm发送{button, InputPwd}消息时,
%% zy_test_fsm会执行zy_test_fsm:close({button, InputPwd},{[],Pwd}) {[],Pwd}是之前init时的StateData
gen_fsm:send_event(zy_test_fsm, {button, InputPwd}).
%% send_event 当前状态下处理该事件 -> StateName(Event,StateData)
%% send_all_state_event 所有状态下该事件的处理方式都是一样的 -> handle_event(NewState, _OldState, StateData)
close({button, InputPwd}, {OldInput, Pwd}) ->
if
InputPwd =:= Pwd ->
io:format("密码正确! ~n", []),
{next_state, open, {[], Pwd}}; %% 密码正确开门
length(OldInput) >= 2 ->
io:format("密码已经输错三次了,已被锁定5秒. ~n", []),
{next_state, lock, {OldInput, Pwd}, 5000}; %% 输错三次了,超时5秒后执行lock状态的timeout,执行解锁,再次期间有消息进入则不会执行timeout
true ->
%% 密码不正确没满足三次,历史输入存进数据
io:format("密码错误! ~n", []),
{next_state, close, {[InputPwd | OldInput], Pwd}}
end.
lock({button, _InputPwd}, {OldInput, Pwd}) ->
io:format("状态被锁定 ~n", []),
{next_state, lock, {OldInput, Pwd}};
%% 输错密码5秒超时后执行的解锁
lock(timeout, {_, Pwd}) ->
io:format("5秒已过,可以继续输入密码了 ~n", []),
{next_state, close, {[], Pwd}}.
%% 开门
open({button, _InputPwd}, {_, Pwd}) ->
io:format("开门了.. ~n", []),
{next_state, open, {[], Pwd}}.
stop() ->
gen_fsm:send_all_state_event(zy_test_fsm, stop).
handle_event(stop, _StateName, Data) ->
{stop, normal, Data};
handle_event(NewState, StateName, Data) ->
io:format("handle_event...~p ~n",[{NewState,StateName}]),
{next_state, StateName, Data}.
handle_sync_event(NewState, From, StateName, Data) ->
io:format("handle_sync_event, for process: ~p... ~n", [{NewState, From, StateName, Data}]),
{next_state, StateName, Data}.
handle_info(Info, StateName, Data) ->
io:format("handle_info...~p~n",[{Info, StateName, Data}]),
{next_state, StateName, Data}.
terminate(shutdown, _StateName, _Data) ->
ok;
terminate(normal, _StateName, _Data) ->
ok.
1> fsm_test:start_link(banana). %% <-新建一个密码为banana的开门状态机
{ok,<0.5523.0>}
2> fsm_test:button(hasaki).
密码错误!
ok
3> fsm_test:button(hasaki).
密码错误!
ok
4> fsm_test:button(hasaki). %% <-注意这里错了三次后,再次输入错误密码则锁死,需要等5秒让它自己重置次数
密码已经输错三次了,已被锁定5秒.
ok
5秒已过,可以继续输入密码了. %% <-触发timeout超时,可以继续输密码了
5> fsm_test:button(hasaki).
密码错误!
ok
6> fsm_test:button(hasaki).
密码错误!
ok
7> fsm_test:button(hasaki).
密码已经输错三次了,已被锁定5秒.
ok
8> fsm_test:button(hasaki). %% <-5秒超时内有了新消息,无法触发timeout,状态被锁死
状态被锁定
ok
个人感觉挺好玩,就是控制超时还是用自己的业务逻辑控制比较好,这里的timeout机制感觉大部分情况都不太适合。
其次,gen_fsm已经被官方建议替换为 gen_statem
参考了这篇文章:https://blog.csdn.net/xyj0663/article/details/8510488 是转载的,原博文被删了,讲的可以
不过我看他的这篇转载文章应该参考的https://www.iteye.com/blog/cryolite-1451070 这篇文章