状态模式是一种行为设计模式,让你能在一个对象的内部状态变化时改变其行为,使其看上去就像改变了自身所属的类一样。
解决什么问题?
- 如果对象需要根据自身当前状态进行不同行为, 同时状态的数量非常多且与状态相关的代码会频繁变更的话, 可使用状态模式。
- 如果某个类需要根据成员变量的当前值改变自身行为, 从而需要使用大量的条件语句时, 可使用该模式。
- 当相似状态和基于条件的状态机转换中存在许多重复代码时, 可使用状态模式。
优势:
- 单一职责原则。 将与特定状态相关的代码放在单独的类中。
- 开闭原则。 无需修改已有状态类和上下文就能引入新状态。
- 通过消除臃肿的状态机条件语句简化上下文代码。
劣势:
- 如果状态机只有很少的几个状态, 或者很少发生改变, 那么应用该模式可能会显得小题大作。
实现步骤:
- 定义
实体类接口 - 定义
实体类,关联实体类与上下文类。实体类可修改上下文类的状态 - 定义
上下文类,关联上下文类与实体类。通过上下类接口调用实体类接口 - 客户端代码关联
实体类对象和上下文类对象,实现状态启动和转移
下面是实现代码:
package main
import "fmt"
// 定义实体类接口
type state interface {
render()
publish() // 状态转移函数
}
// 定义上下文类
type document struct {
s state
isUser bool
content string
}
func (d *document) render() {
d.s.render()
}
func (d *document) publish() {
d.s.publish()
}
// 关联实体类
func (d *document) changeState(s state) {
d.s = s
}
// 定义实体类
type draft struct {
d *document
}
func (d *draft) render() {
fmt.Println("draft render: ", d.d.content)
}
func (d *draft) publish() {
if d.d.isUser {
d.d.changeState(&moderation{
d: d.d,
})
} else {
d.d.changeState(&published{
d: d.d,
})
}
}
type moderation struct {
d *document
}
func (m *moderation) render() {
fmt.Println("moderation render: ", m.d.content)
}
func (m *moderation) publish() {
if len(m.d.content) > 5 {
m.d.changeState(&published{
d: m.d,
})
} else {
fmt.Println("moderation failed. content is shorter than 5")
m.d.changeState(&draft{
d: m.d,
})
}
}
type published struct {
d *document
}
func (p *published) render() {
fmt.Println("published render: ", p.d.content)
}
func (p *published) publish() {
if len(p.d.content) < 10 {
fmt.Println("published failed. content is shorter than 10")
p.d.changeState(&draft{
d: p.d,
})
} else {
fmt.Println("published successfully!!!")
}
}
func main() {
doc := document{
isUser: true,
content: "123",
}
dr := draft{d: &doc}
doc.s = &dr
// 第一次发布
fmt.Println("first publish")
doc.render()
doc.publish()
doc.render()
doc.publish()
fmt.Println()
// 第二次发布
fmt.Println("second publish")
doc.content = "123456"
doc.render()
doc.publish()
doc.render()
doc.publish()
doc.render()
doc.publish()
fmt.Println()
// 第三此发布
fmt.Println("third publish")
doc.content = "123456789A"
doc.render()
doc.publish()
doc.render()
doc.publish()
doc.render()
doc.publish()
}
输出:
first publish
draft render: 123
moderation render: 123
moderation failed. content is shorter than 5
second publish
draft render: 123456
moderation render: 123456
published render: 123456
published failed. content is shorter than 10
third publish
draft render: 123456789A
moderation render: 123456789A
published render: 123456789A
published successfully!!!