状态模式避免了一个程序中出现大量的switch case/if
。实际上是将状态的转换拆分到不同的子类中实现。
在实现子类的时候,只需要考虑当前状态,传入的条件,下一个状态。比较节省脑容量。
这是一个go实现的状态模式的例子。
/* statemode 这是一个状态模式的示例
状态根据当前的信息转换
normal -心跳过期-> preOffline -继续过期-> offline
-心跳恢复-> normal
*/
package statemode
import (
"fmt"
"math"
"time"
"github.com/sirupsen/logrus"
)
type State interface {
// Change 状态根据传入的info进行改变,返回新的状态或者错误
Change(info map[string]string) (State, error)
}
type NormalState struct {
}
func (n *NormalState) Change(info map[string]string) (State, error) {
ttime, err := datautils.ToInt(info["time"])
if err != nil {
return nil, err
}
// 判定心跳时间如果超过当前时间3秒,则状态转换为preOffline
if abs(time.Now().Unix()-int64(ttime)) > 3 {
return &PreOfflineState{}, nil
}
return n, nil
}
type PreOfflineState struct {
}
func (p *PreOfflineState) Change(info map[string]string) (State, error) {
ttime, err := datautils.ToInt(info["time"])
if err != nil {
return nil, err
}
// 当在preOffline的时候,心跳继续过期3秒,转换为offline状态
if abs(time.Now().Unix()-int64(ttime)) > 3 {
return &OfflineState{}, nil
} else {
// 当在preOffline的时候,心跳恢复,转换为normal状态
return &NormalState{}, nil
}
}
type OfflineState struct {
}
func (o *OfflineState) Change(info map[string]string) (State, error) {
// offline状态为最终状态,不能再转换
return nil, fmt.Errorf("offline state cannot change")
}
func abs(a interface{}) float64 {
v, err := datautils.ToFloat64(a)
if err != nil {
logrus.Fatal(err)
}
return math.Abs(v)
}
测试用例
代码语言:javascript复制// statemode 这是一个状态模式的示例
package statemode
import (
"reflect"
"testing"
"time"
"github.com/sirupsen/logrus"
)
func TestNormalState_Change(t *testing.T) {
initState := &NormalState{}
normalState, err := initState.Change(map[string]string{
"time": datautils.ToStr(time.Now().Unix()),
})
if err != nil {
t.Fatal(err)
}
_, ok := normalState.(*NormalState)
if !ok {
t.Fatal()
}
preOfflineState, err := normalState.Change(map[string]string{
"time": datautils.ToStr(time.Now().Unix() - 10),
})
if err != nil {
t.Fatal(err)
}
_, ok = preOfflineState.(*PreOfflineState)
logrus.Infof("nextState preoffline:%v", reflect.TypeOf(preOfflineState))
if !ok {
t.Fatal()
}
offlineState, err := preOfflineState.Change(map[string]string{
"time": datautils.ToStr(time.Now().Unix() - 10),
})
if err != nil {
t.Fatal(err)
}
_, ok = offlineState.(*OfflineState)
logrus.Infof("nextState offline:%v", reflect.TypeOf(offlineState))
if !ok {
t.Fatal()
}
normalState, err = preOfflineState.Change(map[string]string{
"time": datautils.ToStr(time.Now().Unix()),
})
if err != nil {
t.Fatal(err)
}
_, ok = normalState.(*NormalState)
logrus.Infof("nextState back to normal:%v", reflect.TypeOf(normalState))
if !ok {
t.Fatal()
}
}