使用Go实现23种设计模式——行为型模式(下)
访问者模式
在不改变各元素的类的前提下定义作用于这些元素的新操作
适用场景
- 数据结构相对稳定的对象,且需要经常在此结构上定义新操作
Go语言实现
type Number struct {
Numbers []int
}
func (n *Number) Do(v IVisitor) {
v.Do(n.Numbers)
}
type IVisitor interface {
Do([]int)
}
type AddVisitor struct{}
func (v *AddVisitor) Do(numbers []int) {
sum := 0
for _, n := range numbers {
sum += n
}
fmt.Printf("sum: %d\n", sum)
}
type TimesVisitor struct{}
func (v *TimesVisitor) Do(numbers []int) {
res := 1
for _, n := range numbers {
res *= n
}
fmt.Printf("res: %d\n", res)
}
func main() {
a := Number{Numbers: []int{1, 2, 3, 4}}
a.Do(&AddVisitor{})
a.Do(&TimesVisitor{})
}
访问者模式优点
- 增加新的操作简单
访问者模式缺点
- 增加新的数据结构困难
模板方法模式
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤
适用场景
- 模板模式解决了一些方法通用,却在每个子类都重新写一遍的这些方法
Go语言实现
type Notify struct {
}
func (n *Notify) Send() {
fmt.Println("send error")
}
type SMSNotify struct {
Notify
}
func (n *SMSNotify) Send() {
fmt.Println("send sms success")
}
type EMailNotify struct {
Notify
}
func (n *EMailNotify) Send() {
fmt.Println("send email success")
}
func main() {
a := SMSNotify{}
a.Send()
b := EMailNotify{}
b.Send()
}
模板方法模式优点
- 封装不变的部分,扩展可变部分
- 提取公共代码,便于维护
- 行为由父类控制,子类实现
模板方法模式缺点
- 每一个不同的实例,都需要一个子类来实现,导致类的个数增加
策略模式
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换
适用场景
- 一个类定义了多种行为,并且这些行为以多个if…else形式出现
Go语言实现
type Operation interface {
Do(n1, n2 int) int
}
type OperationAdd struct {
}
func NewOperationAdd() *OperationAdd {
return &OperationAdd{}
}
func (o *OperationAdd) Do(n1, n2 int) int {
return n1 + n2
}
type OperationSubtract struct {
}
func NewOperationSubtract() *OperationSubtract {
return &OperationSubtract{}
}
func (o *OperationSubtract) Do(n1, n2 int) int {
return n1 -n2
}
type Context struct {
operation Operation
}
func NewContext(operation Operation) *Context {
return &Context{operation: operation}
}
func (c *Context) Do(n1, n2 int) int {
return c.operation.Do(n1, n2)
}
func main() {
a := NewContext(NewOperationAdd())
a.Do(1, 2)
b := NewContext(NewOperationSubtract())
b.Do(3, 1)
}
策略模式优点
- 避免使用多重判断语句
- 算法可自由切换
- 扩展性好
策略模式缺点
- 客户端需要理解所有策略算法的区别,以便选择合适的算法类
- 策略类很多,增加维护难度
状态模式
允许一个对象在其内部状态改变时改变它的行为
适用场景
- 代码中包含大量与对象状态有关的条件语句
Go语言实现
type Account struct {
Action State
status string
}
func NewAccount(status string) *Account {
a := &Account{status: status}
a.initState()
return a
}
func (a *Account) initState() {
if a.status == "normal" {
a.Action = &NormalState{}
} else {
a.Action = &BannedState{}
}
}
type State interface {
Login()
}
type NormalState struct{}
func (s *NormalState) Login() {
fmt.Println("登陆成功")
}
type BannedState struct{}
func (s *BannedState) Login() {
fmt.Println("账号封禁,禁止登陆")
}
func main() {
a := NewAccount("normal")
a.Action.Login()
}
状态模式优点
- 封装了转换规则
- 将所有与状态相关的行为放到一个类中,方便的增加状态
- 允许状态转换逻辑和状态对象合成一体,而不是巨大的条件语句块
状态模式缺点
- 增加了系统中类和对象的个数,导致系统运行开销变大
- 状态模式的结构与实现都比较复杂
- 对"开闭原则"的支持不友好