头图

前两篇介绍了设计模式那些事(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...

爆裂Gopher
20 声望11 粉丝

一篇文章讲明白一个知识点,每月更新,欢迎关注与交流。