From ab8d8e908fbf84699931da09d04a62e7a06527e8 Mon Sep 17 00:00:00 2001 From: nynicg Date: Fri, 3 May 2019 10:48:46 +0800 Subject: [PATCH] state pattern --- README.md | 4 +- behavioral/command/main.go | 11 ++-- behavioral/memento/main.go | 16 ++++- behavioral/state/main.go | 84 +++++++++++++++++++++++++ behavioral/state/problem.go | 118 ++++++++++++++++++++++++++++++++++++ 5 files changed, 225 insertions(+), 8 deletions(-) create mode 100644 behavioral/state/main.go create mode 100644 behavioral/state/problem.go diff --git a/README.md b/README.md index e96ca76..6b76f10 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,9 @@ A curated collection of idiomatic design & application patterns for Go language. | [Memento](/behavioral/memento/main.go) | Generate an opaque token that can be used to go back to a previous state | ✔ | | [Observer](/behavioral/observer.md) | Provide a callback for notification of events/changes to data | ✔ | | [Registry](/behavioral/registry.md) | Keep track of all subclasses of a given class | ✘ | -| [State](/behavioral/state.md) | Encapsulates varying behavior for the same object based on its internal state | ✘ | +| [State](/behavioral/state.md) | Encapsulates varying behavior for the same object based on its internal state | ✔ | | [Strategy](/behavioral/strategy.md) | Enables an algorithm's behavior to be selected at runtime | ✔ | -| [Template](/behavioral/template.md) | Defines a skeleton class which defers some methods to subclasses | ✘ | +| [Template](/behavioral/template.md) | Defines a skeleton class which defers some methods to subclasses | ✔ | | [Visitor](/behavioral/visitor.md) | Separates an algorithm from an object on which it operates | ✘ | ## Synchronization Patterns diff --git a/behavioral/command/main.go b/behavioral/command/main.go index b32fe61..6747dd5 100644 --- a/behavioral/command/main.go +++ b/behavioral/command/main.go @@ -69,13 +69,14 @@ func main(){ attk := CommandAttack{playerA} escp := CommandEscape{playerA} - invoker.PushCommands(attk ,escp ,escp ,attk) + invoker.PushCommands(attk ,escp ,escp ,attk ,escp) invoker.CallCommands() // output: /* - icg opened fire - icg escape - icg escape - icg opened fire + icg opened fire + icg escape + icg escape + icg opened fire + icg escape */ } diff --git a/behavioral/memento/main.go b/behavioral/memento/main.go index 5e6428a..047e635 100644 --- a/behavioral/memento/main.go +++ b/behavioral/memento/main.go @@ -8,10 +8,14 @@ import ( "log" ) +// originator type Player struct { + // 需要记录的数据可以考虑单独封装 + // type Pos struct{X,Y int} X,Y int - Name string + // other info + Name string } func (p *Player)MoveTo(x,y int){ @@ -31,10 +35,12 @@ func (p *Player)Restore(m PlayerMemento){ p.Y = m.Y } +// memento type PlayerMemento struct { X,Y int } +// caretaker type PlayerCareTaker struct { MementoList *list.List } @@ -75,4 +81,12 @@ func main(){ icg.Restore(ct.RemoveLast()) log.Println(icg.X ,icg.Y) + /* + output: + 2019/05/02 18:18:03 114 514 + 2019/05/02 18:18:03 810 19 + 2019/05/02 18:18:03 0 0 + 2019/05/02 18:18:03 810 19 + 2019/05/02 18:18:03 114 514 + */ } diff --git a/behavioral/state/main.go b/behavioral/state/main.go new file mode 100644 index 0000000..b586d18 --- /dev/null +++ b/behavioral/state/main.go @@ -0,0 +1,84 @@ +// 状态模式 state pattern. +// 与策略模式在一些场景可以通用,但策略模式倾向于调用者根据情况手动改变内部策略以切换算法, +// 状态模式倾向于由Context内部自行管理状态,只需要设定初始状态即可,不需要手动切换. +// +// 以下是以跷跷板seesaw为例,分为[左侧高LeftState|右侧高RightState] +package main + +import ( + "math/rand" + "time" +) + +// state +type SeesawState interface { + LiftLeftSide(S *Seesaw) + LiftRightSide(S *Seesaw) +} + +type LeftState struct {} + +func (LeftState) LiftLeftSide(S *Seesaw) { + println("↑LEFT > left side wads already lifted") +} + +func (l LeftState) LiftRightSide(S *Seesaw) { + println("RIGHT↑ > lift right side") + S.State = RightState{} +} + +type RightState struct {} + +func (r RightState) LiftLeftSide(S *Seesaw) { + println("↑LEFT > lift left side") + S.State = LeftState{} +} + +func (RightState) LiftRightSide(S *Seesaw) { + println("RIGHT↑ > right side wads already lifted") +} + +// context +type Seesaw struct { + State SeesawState +} + +func (s *Seesaw)MakeLeftUp(){ + s.State.LiftLeftSide(s) +} + +func (s *Seesaw)MakeRightUp(){ + s.State.LiftRightSide(s) +} + +func main(){ + // init left + seesaw := &Seesaw{ + State:LeftState{}, + } + + rand.Seed(time.Now().UnixNano()) + for i:=0 ;i<10 ;i++{ + if rand.Intn(2) == 1{ + // ▄▃▂ + seesaw.MakeLeftUp() + }else{ + // ▂▃▄ + seesaw.MakeRightUp() + } + } + /* + output: + RIGHT↑ > lift right side + RIGHT↑ > right side wads already lifted + RIGHT↑ > right side wads already lifted + ↑LEFT > lift left side + ↑LEFT > left side wads already lifted + ↑LEFT > left side wads already lifted + ↑LEFT > left side wads already lifted + RIGHT↑ > lift right side + RIGHT↑ > right side wads already lifted + ↑LEFT > lift left side + */ +} + diff --git a/behavioral/state/problem.go b/behavioral/state/problem.go new file mode 100644 index 0000000..159c2d6 --- /dev/null +++ b/behavioral/state/problem.go @@ -0,0 +1,118 @@ +// 存疑部分. +// +// 这里是以玩家的健康状态为基准,影响生命回复和受到伤害的例子,有三个状态[健康HealthyState|受伤WoundedState|死亡DeadState]. +// 和main.go中的跷跷板例子由行为驱动状态变化不同.以下例子的行为是受到数据影响的,结果就是达不到完全消除if-else的效果 +// +// 疑问: +// 1. 很多地方说状态模式可以消除if-else,事实上要做到自行的状态切换很多时候还是会用到if-else,这里的消除应当是指 +// 将巨大的条件语句拆分开(?) +// 2. 还是说这种由数据决定状态的事务逻辑下不适合使用状态模式 +package main + +import ( + "errors" + "fmt" +) + +// state +type PlayerState interface { + Heal(p *Player) error + Hurt(p *Player ,dmg int) +} + +type HealthyState struct {} + +func (h HealthyState)Heal(p *Player) error{ + return nil +} + +func (h HealthyState)Hurt(p *Player ,dmg int){ + if dmg > 0 && dmg < p.MaxHealth{ + p.Health = p.Health - dmg + p.State = WoundedState{} + }else if dmg > p.MaxHealth{ + p.Health = 0 + p.State = DeadState{} + } +} + +type WoundedState struct {} + +func (WoundedState)Heal(p *Player) error{ + if p.Health >= p.MaxHealth - 5{ + fmt.Printf("healing from %d to %d\n" ,p.Health ,p.MaxHealth) + p.State = HealthyState{} + p.Health = p.MaxHealth + }else{ + fmt.Printf("healing from %d to %d\n" ,p.Health ,p.Health+5) + p.Health = p.Health + 5 + } + return nil +} + +func (h WoundedState)Hurt(p *Player ,dmg int){ + if p.Health > dmg{ + p.Health = p.Health - dmg + }else { + p.State = DeadState{} + p.Health = 0 + } +} + +type DeadState struct {} + +func (DeadState)Heal(P *Player) error{ + return errors.New("you are dead") +} + +func (DeadState)Hurt(P *Player ,dmg int){} + +// context +type Player struct { + Health int + MaxHealth int + State PlayerState +} + +func (p *Player)HealPlayer()error{ + return p.State.Heal(p) +} + +func (p *Player)HurtPlayer(damage int){ + fmt.Printf("damage %d\n" ,damage) + p.State.Hurt(p ,damage) +} + +//func main(){ +// player := &Player{ +// Health:100, +// MaxHealth:100, +// State:HealthyState{}, +// } +// +// rand.Seed(time.Now().UnixNano()) +// for i:=0 ;i<10 ;i++{ +// if err := player.HealPlayer();err != nil{ +// fmt.Println(err) +// break +// } +// player.HurtPlayer(rand.Intn(30)) +// } +// /* +// output: +// damage 28 +// healing from 72 to 77 +// damage 29 +// healing from 48 to 53 +// damage 15 +// healing from 38 to 43 +// damage 1 +// healing from 42 to 47 +// damage 19 +// healing from 28 to 33 +// damage 27 +// healing from 6 to 11 +// damage 16 +// you are dead +// */ +//}