前两篇介绍了设计模式那些事(2)——创建型模式在Go中的应用、设计模式那些事(4)——常见结构型模式在Go中的应用,接下来我们看一下最后一种类型——行为型的设计模式。
创建型设计模式主要解决“对象的创建”问题,结构型设计模式主要解决“类或对象的组合或组装”问题,那行为型设计模式主要解决的就是“类或对象之间的交互”问题。
行为型模式比较多,有 11 种,它们分别是:观察者模式、模板模式、策略模式、职责链模式、迭代器模式、状态模式、访问者模式、备忘录模式、命令模式、解释器模式、中介模式,今天重点介绍一下前四种最常见的模式。
一、观察者模式
定义
观察者模式允许你定义一种订阅机制,可在对象事件发生时通知多个 “观察” 该对象的其他对象。
使用场景
小到代码层面的解耦,大到架构层面的系统解耦,再或者一些产品的设计思路,都有这种模式的影子,比如,邮件订阅、RSS Feeds,本质上都是观察者模式。
实现
同步阻塞是最经典的实现方式,主要是为了代码解耦;
异步非阻塞除了能实现代码解耦之外,还能提高代码的执行效率;
进程间的观察者模式解耦更加彻底,一般是基于消息队列来实现,用来实现不同进程间的被观察者和观察者之间的交互。
下面实现一个同步阻塞的发布者、订阅者模型
package main
import "fmt"
type ISubject interface {
register(observer Observer)
notifyAll()
}
type Blogger struct {
observerList []Observer
name string
}
func newBlogger(name string) * Blogger {
return &Blogger{
name: name,
}
}
func (i * Blogger) Publish() {
fmt.Printf("Blogger %s has been updated\n", i.name)
i.notifyAll()
}
func (i * Blogger) register(o Observer) {
i.observerList = append(i.observerList, o)
}
func (i * Blogger) notifyAll() {
for _, observer := range i.observerList {
observer.update(i.name)
}
}
type Observer interface {
update(string)
getID() string
}
type Reader struct {
id string
}
func (c *Reader) update(bloggerName string) {
fmt.Printf("Sending notice to reader %s for Blogger %s\n", c.id, bloggerName)
}
func (c *Reader) getID() string {
return c.id
}
func main() {
blogger := newBlogger("韩寒")
observerFirst := &Reader{id: "abc@gmail.com"}
observerSecond := &Reader{id: "xyz@gmail.com"}
blogger.register(observerFirst)
blogger.register(observerSecond)
blogger.Publish()
}
输出
Blogger 韩寒 has been updated
Sending notice to reader abc@gmail.com for Blogger 韩寒
Sending notice to reader xyz@gmail.com for Blogger 韩寒
二、模板模式
定义
模板模式在一个方法中定义一个通用的算法(业务逻辑)骨架,并将某些步骤推迟到子类中实现。
模板模式有两大作用:复用和扩展。其中复用指的是,所有的子类可以复用父类中提供的模板方法的代码。扩展指的是,框架通过模板模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能。
使用场景
当多个类的算法具有通用步骤,除一些细微不同之外几乎完全一样时,你可使用该模式。
实现
线上购物和线下购物有着相同的步骤,这里定义一个购物类的接口、抽出一套通用的模版,线下购物和线上购物类都继承这套模版,分别实现这些步骤,这样不用分别写两套业务逻辑,也不用在同一套流程中做很多的 if else 判断,实现了代码的复用和扩展性。
package main
import "fmt"
//抽象类,购物,包裹一个模板的全部实现步骤
type IShopping interface {
goToMall() //开始购物
browseProducts() //浏览商品
addShoppingCart() //加入购物车
pay() //结算
finish() //购物结束
haveDinner() //餐厅吃饭
WantHaveDinner() bool //是否在餐厅吃饭
}
//封装一套流程模板,让具体的制作流程继承且实现
type template struct {
s IShopping
}
//封装的固定模板
func (t *template) GoShoping() {
t.s.goToMall()
t.s.browseProducts()
t.s.addShoppingCart()
t.s.pay()
t.s.finish()
//子类可以重写该方法来决定是否执行下面动作
if t.s.WantHaveDinner() == true {
t.s.haveDinner()
}
}
//具体的模板子类 线下购物
type OfflineShopping struct {
template //继承模板
}
func NewOfflineShopping() *OfflineShopping {
offlineShopping := new(OfflineShopping)
offlineShopping.s = offlineShopping
return offlineShopping
}
func (mc *OfflineShopping) goToMall() {
fmt.Println("开车前往商场")
}
func (mc *OfflineShopping) browseProducts() {
fmt.Println("在商场浏览商品")
}
func (mc *OfflineShopping) addShoppingCart() {
fmt.Println("将中意的商品加入手推车中")
}
func (mc *OfflineShopping) pay() {
fmt.Println("前往收银台付款")
}
func (mc *OfflineShopping) finish() {
fmt.Println("购物结束,将商品带回家")
}
func (mc *OfflineShopping) haveDinner() {
fmt.Println("在商场吃个西餐")
}
func (mc *OfflineShopping) WantHaveDinner() bool {
return true //启动Hook条件
}
//具体的模板子类 线上购物
type OnlineShopping struct {
template //继承模板
}
func NewOnlineShopping() *OnlineShopping {
onlineShopping := new(OnlineShopping)
onlineShopping.s = onlineShopping
return onlineShopping
}
func (mt *OnlineShopping) goToMall() {
fmt.Println("打开购物App")
}
func (mt *OnlineShopping) browseProducts() {
fmt.Println("浏览商品页")
}
func (mt *OnlineShopping) addShoppingCart() {
fmt.Println("将中意的商品加入购物车中")
}
func (mt *OnlineShopping) pay() {
fmt.Println("线上付款")
}
func (mc *OnlineShopping) finish() {
fmt.Println("购物结束,等待商品送货上门")
}
func (mc *OnlineShopping) haveDinner() {
fmt.Println("点个外卖吃吃")
}
func (mc *OnlineShopping) WantHaveDinner() bool {
return true //启动Hook条件
}
func main() {
//1. 线下购物
offline := NewOfflineShopping()
offline.GoShoping()
fmt.Println("------------")
//2. 线上购物
online := NewOnlineShopping()
online.GoShoping()
}
输出
开车前往商场
在商场浏览商品
将中意的商品加入手推车中
前往收银台付款
购物结束,将商品带回家
在商场吃个西餐
------------
打开购物App
浏览商品页
将中意的商品加入购物车中
线上付款
购物结束,等待商品送货上门
点个外卖吃吃
三、策略模式
定义
策略模式用来解耦策略的定义、创建、使用,实际上,一个完整的策略模式就是由这三个部分组成的。
策略类的定义比较简单,包含一个策略接口和一组实现这个接口的策略类。
策略的创建由工厂类来完成,封装策略创建的细节。
使用场景
当你想使用对象中各种不同的算法变体,并希望能在运行时切换算法时,可使用策略模式。
我们还可以通过策略模式来移除 if-else 分支判断。实际上,这得益于策略工厂 类,更本质上点讲,是借助“查表法”,根据 type 查表(map)替代根据 type 分支判断(进行实例化)。
实现
商家有两种不同的活动策略,一种是打折,一种是满减,在后台配置以后就可以用于各种营销活动之中:
package main
import (
"fmt"
"math"
)
//销售策略
type SellStrategy interface {
//根据原价得到售卖价
GetPrice(price float64) float64
}
type StrategyA struct {}
func (sa *StrategyA) GetPrice(price float64) float64 {
fmt.Println("执行策略A, 打8折")
return price * 0.8;
}
type StrategyB struct {}
func (sb *StrategyB) GetPrice(price float64) float64 {
fmt.Println("执行策略B, 每满200减50")
n:=int(math.Floor(price)) / 200
if price >= 200 {
price -= float64(n)*50
}
return price;
}
//环境类
type Goods struct {
Price float64
Strategy SellStrategy
}
func (g *Goods) SetStrategy(s SellStrategy) {
g.Strategy = s
}
func (g *Goods) SellPrice() float64 {
fmt.Println("原价", g.Price)
return g.Strategy.GetPrice(g.Price)
}
func NewStrategy(t string) SellStrategy {
switch t {
case "A":
return &StrategyA{}
case "B":
return &StrategyB{}
}
return nil
}
func main() {
nike := Goods{
Price: 1199.0,
}
//淘宝执行策略A
strategyA:=NewStrategy("A")
nike.SetStrategy(strategyA)
fmt.Println("淘宝nike鞋卖", nike.SellPrice())
//京东执行策略B
strategyB:=NewStrategy("B")
nike.SetStrategy(strategyB)
fmt.Println("京东nike鞋卖", nike.SellPrice())
}
输出
原价 1199
执行策略A, 打8折
淘宝nike鞋卖 959.2
原价 1199
执行策略B, 每满200减50
京东nike鞋卖 949
四、职责链模式
定义
在职责链模式中,多个处理器依次处理同一个请求。一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理器处理完后再传递给 C 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作职责链模式。
当然,在实际的开发中,也存在对这个模式的变体,那就是请求不会中途终止传递, 而是会被所有的处理器都处理一遍。
使用场景
职责链模式最常用来开发框架的过滤器和拦截器,让框架的使用者在不需要修改框架源码的情况下,添加新的过滤拦截功能。
这也体现了对扩展开放、对修改关闭的设计原则。
实现
这里实现一个敏感词过滤器
package main
import "fmt"
// SensitiveWordFilter 敏感词过滤器,判定是否是敏感词
type SensitiveWordFilter interface {
Filter(content string) bool
}
// SensitiveWordFilterChain 职责链
type SensitiveWordFilterChain struct {
filters []SensitiveWordFilter
}
// AddFilter 添加一个过滤器
func (c *SensitiveWordFilterChain) AddFilter(filter SensitiveWordFilter) {
c.filters = append(c.filters, filter)
}
// Filter 执行过滤
func (c *SensitiveWordFilterChain) Filter(content string) bool {
for _, filter := range c.filters {
// 如果发现敏感直接返回结果
if filter.Filter(content) {
fmt.Println("发现敏感词,请修改后重新发布")
return true
}
}
fmt.Println("内容正常,可以发布")
return false
}
// AdSensitiveWordFilter 广告
type AdSensitiveWordFilter struct{}
// Filter 实现过滤算法
func (f *AdSensitiveWordFilter) Filter(content string) bool {
// TODO: 实现算法
fmt.Println("发现广告敏感词")
return true
}
// PoliticalWordFilter 政治敏感
type PoliticalWordFilter struct{}
// Filter 实现过滤算法
func (f *PoliticalWordFilter) Filter(content string) bool {
// TODO: 实现算法
fmt.Println("没有发现政治敏感词")
return false
}
func main(){
chain := &SensitiveWordFilterChain{}
chain.AddFilter(&PoliticalWordFilter{})
chain.AddFilter(&AdSensitiveWordFilter{})
// chain.AddFilter(&PoliticalWordFilter{})
chain.Filter("xxx")
}
输出
没有发现政治敏感词
发现广告敏感词
发现敏感词,请修改后重新发布
五、总结
设计模式要干的事情就是解耦,创建型模式是将创建和使用代码解耦,结构型模式是将不同功能代码解耦,行为型模式是将不同的行为代码解耦。
借助设计模式,我们利用更好的代码结构,将一大坨代码拆分成职责更单一的小类,让其满足开闭原则、高内聚松耦合等特性,以此来控制和应对代码的复杂性,提高代码的可扩展性。
参考资料:
1、《设计模式之美》
2、https://refactoringguru.cn/de...
3、Easy搞掂Golang设计模式
4、https://lailin.xyz/post/go-de...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。