手撸golang 行为型设计模式 状态模式

缘起

最近温习设计模式
拜读谭勇德的<<设计模式就该这样学>>
本系列笔记拟采纳golang练习之

状态模式

状态模式(State Pattern)也叫作状态机模式(State Machine Pattern),容许对象在外部状态产生扭转时扭转它的行为,对象看起来如同批改了它的类,属于行为型设计模式。状态模式次要蕴含3个角色。(1)环境类角色(Context):定义客户端须要的接口,外部保护一个以后状态实例,并负责具体状态的切换。(2)形象状态角色(IState):定义该状态下的行为,能够有一个或多个行为。(3)具体状态角色(ConcreteState):具体实现该状态对应的行为,并且在须要的状况下进行状态切换。(摘自 谭勇德 <<设计模式就该这样学>>)

场景

  • 某业务零碎, 须要对接某对象存储系统, 以对立治理大量的文件数据
  • 因为该对象存储系统只提供了http api文档, 因而须要自行封装文件读写api
  • 码农王二狗接到Leader张阿蛋下达的开发工作, 啃了几遍文档, 感觉胸有成竹

    • 王二狗: 张哥, 这个读写api, 是不是就写一个门面, 一个办法封装一个api就行了, 感觉性能不简单
    • 张阿蛋: 基本上是这样的. 不过你有没有留神到, 文档上有注意事项, 就是关上文件的时候, 必须确定文件的应用形式?
    • 王二狗: 啥? 哦...我看看, 还真是有这么一条, 不过这个有影响吗, 不就是多传一个参数吗?
    • 张阿蛋: 当然有影响, 如果你以读形式关上, 那么后续是不承受写入的. 如果你以写形式关上, 后续是不能读取的.
    • 王二狗: 哦, 那预计要好好封装一下http api的报错信息?
    • 张阿蛋: 这个是必定要的, 通信不免有出错的时候. 然而, 兴许咱们能够用状态模式, 提前回绝一些非法操作
    • 王二狗: ...不明觉厉, 能对我间接点吗?
    • 张阿蛋: 将文件流对象的办法委托给"未关上", "读取中", "写入中", 和"已敞开"四种状态. 每种状态只响应以后状态下容许的操作, 其余操作间接返回error.
    • 王二狗: 张哥, 强!

设计

  • IFileStream: 文件流API接口
  • iFileStreamContext: 文件流上下文接口, 仅外部应用
  • iFileStreamState: 文件流状态接口, 仅外部应用
  • tMockFileStream: 虚构的文件流API实现类, 实现iFileStream接口 + iFileStreamContext接口
  • tDefaultState: 默认状态 - 未关上状态. 该状态下只容许关上/敞开操作
  • tReadingState: 读取状态. 该状态下只容许读取/敞开操作
  • tWritingState: 写入状态. 该状态下只容许写入/敞开操作
  • tClosedState: 敞开状态. 该状态下只容许敞开操作

单元测试

state_pattern_test.go

package behavioral_patternsimport (    "learning/gooop/behavioral_patterns/state"    "testing")func Test_StatePattern(t *testing.T) {    fnTestFileStream := func(fs state.IFileStream, readonly bool) {        if readonly {            e := fs.OpenRead()            if e != nil {                t.Log(e)            }            e = fs.OpenWrite()            if e != nil {                t.Log(e)            }        } else {            e := fs.OpenWrite()            if e != nil {                t.Log(e)            }            e = fs.OpenRead()            if e != nil {                t.Log(e)            }        }        buffer := make([]byte, 8192)        n,e := fs.Read(buffer)        if e != nil {            t.Log(e)        } else {            t.Logf("%v bytes read", n)        }        n, e = fs.Write(buffer)        if e != nil {            t.Log(e)        } else {            t.Logf("%v bytes written", n)        }        e = fs.Close()        if e != nil {            t.Log(e)        }    }    fnTestFileStream(state.NewMockFileStream("read-only.txt"), true)    fnTestFileStream(state.NewMockFileStream("write-only.txt"), false)}

测试输入

$ go test -v state_pattern_test.go === RUN   Test_StatePatterntDefaultState.OpenRead, file=read-only.txttMockFileStream.Switch, *state.tDefaultState => *state.tReadingState    state_pattern_test.go:18: tReadingState.OpenWrite, already reading read-only.txttReadingState.Read, file=read-only.txt, iBytesRead=8192    state_pattern_test.go:37: 8192 bytes read    state_pattern_test.go:42: tReadingState.Write, cannot write to read-only.txttReadingState.Close, file=read-only.txt, iBytesRead=8192tMockFileStream.Switch, *state.tReadingState => *state.tClosedStatetDefaultState.OpenWrite, file=write-only.txttMockFileStream.Switch, *state.tDefaultState => *state.tWritingState    state_pattern_test.go:28: tWritingState.OpenRead, already writing write-only.txt    state_pattern_test.go:35: tWritingState.Read, cannot read write-only.txttWritingState.Write, file=write-only.txt, written=8192    state_pattern_test.go:44: 8192 bytes writtentWritingState.Close, file=write-only.txt, written=8192tMockFileStream.Switch, *state.tWritingState => *state.tClosedState--- PASS: Test_StatePattern (0.00s)PASSok      command-line-arguments  0.002s

IFileStream.go

文件流API接口

package stateimport "io"type IFileStream interface {    io.ReadWriteCloser    OpenRead() error    OpenWrite() error}

iFileStreamContext.go

文件流上下文接口, 仅外部应用

package statetype iFileStreamContext interface {    File() string    Switch(state iFileStreamState)}

iFileStreamState.go

文件流状态接口, 仅外部应用

package statetype iFileStreamState interface {    IFileStream}

tMockFileStream.go

虚构的文件流API实现类, 实现iFileStream接口 + iFileStreamContext接口

package stateimport (    "fmt"    "reflect")type tMockFileStream struct {    state iFileStreamState    file  string}func NewMockFileStream(file string) IFileStream {    fs := &tMockFileStream{        nil,        file,    }    fs.state = newDefaultState(fs)    return fs}func (me *tMockFileStream) File() string {    return me.file}func (me *tMockFileStream) Switch(st iFileStreamState) {    fmt.Printf("tMockFileStream.Switch, %s => %s\n", reflect.TypeOf(me.state).String(), reflect.TypeOf(st).String())    me.state = st}func (me *tMockFileStream) OpenRead() error {    return me.state.OpenRead()}func (me *tMockFileStream) OpenWrite() error {    return me.state.OpenWrite()}func (me *tMockFileStream) Read(p []byte) (n int, e error) {    return me.state.Read(p)}func (me *tMockFileStream) Write(p []byte) (n int, e error) {    return me.state.Write(p)}func (me *tMockFileStream) Close() error {    return me.state.Close()}

tDefaultState.go

默认状态 - 未关上状态. 该状态下只容许关上/敞开操作

package stateimport (    "errors"    "fmt")type tDefaultState struct {    context iFileStreamContext}func newDefaultState(context iFileStreamContext) iFileStreamState {    return &tDefaultState{        context,    }}func (me *tDefaultState) OpenRead() error {    fmt.Printf("tDefaultState.OpenRead, file=%s\n", me.context.File())    me.context.Switch(newReadingState(me.context))    return nil}func (me *tDefaultState) OpenWrite() error {    fmt.Printf("tDefaultState.OpenWrite, file=%s\n", me.context.File())    me.context.Switch(newWritingState(me.context))    return nil}func (me *tDefaultState) Read(p []byte) (n int, err error) {    return 0, errors.New(fmt.Sprintf("tDefaultState.Read, file not opened, %s", me.context.File()))}func (me *tDefaultState) Write(p []byte) (n int, err error) {    return 0, errors.New(fmt.Sprintf("tDefaultState.Write, file not opened, %s", me.context.File()))}func (me *tDefaultState) Close() error {    fmt.Printf("tDefaultState.Close, file=%s\n", me.context.File())    me.context.Switch(newClosedState(me.context))    return nil}

tReadingState.go

读取状态. 该状态下只容许读取/敞开操作

package stateimport (    "errors"    "fmt")type tReadingState struct {    context iFileStreamContext    iBytesRead  int}func newReadingState(context iFileStreamContext) iFileStreamState {    return &tReadingState{        context,        0,    }}func (me *tReadingState) OpenRead() error {    return errors.New(fmt.Sprintf("tReadingState.OpenRead, already reading %s", me.context.File()))}func (me *tReadingState) OpenWrite() error {    return errors.New(fmt.Sprintf("tReadingState.OpenWrite, already reading %s", me.context.File()))}func (me *tReadingState) Read(p []byte) (n int, err error) {    size := len(p)    me.iBytesRead += size    fmt.Printf("tReadingState.Read, file=%s, iBytesRead=%v\n", me.context.File(), me.iBytesRead)    return size, nil}func (me *tReadingState) Write(p []byte) (n int, err error) {    return 0, errors.New(fmt.Sprintf("tReadingState.Write, cannot write to %s", me.context.File()))}func (me *tReadingState) Close() error {    fmt.Printf("tReadingState.Close, file=%s, iBytesRead=%v\n", me.context.File(), me.iBytesRead)    me.context.Switch(newClosedState(me.context))    return nil}

tWritingState.go

写入状态. 该状态下只容许写入/敞开操作

package stateimport (    "errors"    "fmt")type tWritingState struct {    context iFileStreamContext    written int}func newWritingState(context iFileStreamContext) iFileStreamState {    return &tWritingState{        context,        0,    }}func (me *tWritingState) OpenRead() error {    return errors.New(fmt.Sprintf("tWritingState.OpenRead, already writing %s", me.context.File()))}func (me *tWritingState) OpenWrite() error {    return errors.New(fmt.Sprintf("tWritingState.OpenWrite, already writing %s", me.context.File()))}func (me *tWritingState) Read(p []byte) (n int, err error) {    return 0, errors.New(fmt.Sprintf("tWritingState.Read, cannot read %s", me.context.File()))}func (me *tWritingState) Write(p []byte) (n int, err error) {    size := len(p)    me.written += size    fmt.Printf("tWritingState.Write, file=%s, written=%v\n", me.context.File(), me.written)    return size, nil}func (me *tWritingState) Close() error {    fmt.Printf("tWritingState.Close, file=%s, written=%v\n", me.context.File(), me.written)    me.context.Switch(newClosedState(me.context))    return nil}

tClosedState.go

敞开状态. 该状态下只容许敞开操作

package stateimport (    "errors"    "fmt")type tClosedState struct {    context iFileStreamContext}func newClosedState(context iFileStreamContext) iFileStreamState {    return &tClosedState{        context,    }}func (me *tClosedState) OpenRead() error {    return errors.New(fmt.Sprintf("tClosedState.OpenRead, file(%s) already closed ", me.context.File()))}func (me *tClosedState) OpenWrite() error {    return errors.New(fmt.Sprintf("tClosedState.OpenWrite, file(%s) already closed ", me.context.File()))}func (me *tClosedState) Read(p []byte) (n int, e error) {    return 0, errors.New(fmt.Sprintf("tClosedState.Read, file(%s) already closed ", me.context.File()))}func (me *tClosedState) Write(p []byte) (n int, e error) {    return 0, errors.New(fmt.Sprintf("tClosedState.Write, file(%s) already closed ", me.context.File()))}func (me *tClosedState) Close() error {    return nil}

状态模式小结

状态模式的长处(1)构造清晰:将状态独立为类,打消了冗余的if...else或switch...case语句。(2)将状态转换显示化:通常对象外部都是应用数值类型来定义状态的,    状态的切换通过赋值进行体现,不够直观;    而应用状态类,当切换状态时,是以不同的类进行示意的,转换目标更加明确。(3)状态类职责明确且具备扩展性。状态模式的毛病(1)类收缩:如果一个事物具备很多状态,则会造成状态类太多。(2)状态模式的构造与实现都较为简单,如果使用不当,将导致程序结构和代码的凌乱。(3)状态模式对开闭准则的反对并不太好,对于能够切换状态的状态模式,    减少新的状态类须要批改那些负责状态转换的源码,否则无奈切换到新增状态,    而且批改某个状态类的行为也须要批改对应类的源码。    (摘自 谭勇德 <<设计模式就该这样学>>)

(end)