訂閱通知 | 個人代碼沒有else

嗯,個人代碼沒有else系列,一個設計模式業務真實使用的golang系列。git

前言

本系列主要分享,如何在咱們的真實業務場景中使用設計模式。github

本系列文章主要採用以下結構:golang

  • 什麼是「XX設計模式」?
  • 什麼真實業務場景可使用「XX設計模式」?
  • 怎麼用「XX設計模式」?

雖然本文的題目叫作「訂閱通知」,可是呢,本文卻主要介紹「觀察者模式」如何在真實業務場景中使用。是否是有些不理解?解釋下:設計模式

  • 緣由一,「觀察者模式」其實看起來像「訂閱通知」
  • 緣由二,「訂閱通知」更容易被理解

什麼是「觀察者模式」?

觀察者觀察被觀察者,被觀察者通知觀察者bash

咱們用「訂閱通知」翻譯下「觀察者模式」的概念,結果:微信

「訂閱者訂閱主題,主題通知訂閱者」架構

是否是容易理解多了,咱們再來拆解下這句話,獲得:app

  • 兩個對象
    • 被觀察者 -> 主題
    • 觀察者 -> 訂閱者
  • 兩個動做
    • 訂閱 -> 訂閱者訂閱主題
    • 通知 -> 主題發生變更通知訂閱者

觀察者模式的優點:分佈式

  • 高內聚 -> 不一樣業務代碼變更互不影響
  • 可複用 -> 新的業務(就是新的訂閱者)訂閱不一樣接口(主題,就是這裏的接口)
  • 極易擴展 -> 新增接口(就是新增主題);新增業務(就是新增訂閱者);

其實說白了,就是分佈式架構中使用消息機制MQ解耦業務的優點,是否是這麼一想很容易理解了。函數

什麼真實業務場景能夠用「觀察者模式」?

全部發生變動,須要通知的業務場景

詳細說:只要發生了某些變化,須要通知依賴了這些變化的具體事物的業務場景。

咱們有哪些真實業務場景能夠用「觀察者模式」呢?

好比,訂單逆向流,也就是訂單成立以後的各類取消操做(本文不討論售後),主要有以下取消類型:

訂單取消類型
未支付取消訂單
超時關單
已支付取消訂單
取消發貨單
拒收

在觸發這些取消操做都要進行各類各樣的子操做,顯而易見不一樣的取消操做所涉及的子操做是存在交集的。其次,已支付取消訂單的子操做應該是全部訂單取消類型最全的,其餘類型的複用代碼便可,除了分裝成函數片斷,還有什麼更好的封裝方式嗎?答案:「觀察者模式」。

接着咱們來分析下訂單逆向流業務中的不變

    • 新增取消類型
    • 新增子操做
    • 修改某個子操做的邏輯
    • 取消類型和子操做的對應關係
  • 不變
    • 已存在的取消類型
    • 已存在的子操做(在外界看來)

怎麼用「觀察者模式」?

關於怎麼用,徹底能夠生搬硬套我總結的使用設計模式的四個步驟:

  • 業務梳理
  • 業務流程圖
  • 代碼建模
  • 代碼demo

業務梳理

注:本文於單體架構背景探討業務的實現過程,簡單容易理解。
複製代碼

第一步,梳理出全部存在的的逆向業務的子操做,以下:

全部子操做
修改訂單狀態
記錄訂單狀態變動日誌
退優惠券
還優惠活動資格
還庫存
還禮品卡
退錢包餘額
修改發貨單狀態
記錄發貨單狀態變動日誌
生成退款單
生成發票-紅票
發郵件
發短信
發微信消息

第二步,找到不一樣訂單取消類型和這些子操做的關係,以下:

訂單取消類型(「主題」)(被觀察者) 子操做(「訂閱者」)(觀察者)
取消未支付訂單 -
- 修改訂單狀態
- 記錄訂單狀態變動日誌
- 退優惠券
- 還優惠活動資格
- 還庫存
超時關單 -
- 修改訂單狀態
- 記錄訂單狀態變動日誌
- 退優惠券
- 還優惠活動資格
- 還庫存
- 發郵件
- 發短信
- 發微信消息
已支付取消訂單(未生成發貨單) -
- 修改訂單狀態
- 記錄訂單狀態變動日誌
- 還優惠活動資格(看狀況)
- 還庫存
- 還禮品卡
- 退錢包餘額
- 生成退款單
- 生成發票-紅票
- 發郵件
- 發短信
- 發微信消息
取消發貨單(未發貨) -
- 修改訂單狀態
- 記錄訂單狀態變動日誌
- 修改發貨單狀態
- 記錄發貨單狀態變動日誌
- 還庫存
- 還禮品卡
- 退錢包餘額
- 生成退款單
- 生成發票-紅票
- 發郵件
- 發短信
- 發微信消息
拒收 -
- 修改訂單狀態
- 記錄訂單狀態變動日誌
- 修改發貨單狀態
- 記錄發貨單狀態變動日誌
- 還庫存
- 還禮品卡
- 退錢包餘額
- 生成退款單
- 生成發票-紅票
- 發郵件
- 發短信
- 發微信消息

注:流程不必定徹底準確、全面。

結論:

  • 不一樣的訂單取消類型的子操做存在交集,子操做可被複用。
  • 子操做可被看做「訂閱者」(也就是觀察者)
  • 訂單取消類型可被看做是「主題」(也就是被觀察者)
  • 不一樣子操做(「訂閱者」)(觀察者)訂閱訂單取消類型(「主題」)(被觀察者)
  • 訂單取消類型(「主題」)(被觀察者)通知子操做(「訂閱者」)(觀察者)

業務流程圖

咱們經過梳理的文本業務流程獲得了以下的業務流程圖:

注:本文於單體架構背景探討業務的實現過程,簡單容易理解。
複製代碼

代碼建模

「觀察者模式」的核心是兩個接口:

  • 「主題」(被觀察者)接口Observable
    • 抽象方法Attach: 增長「訂閱者」
    • 抽象方法Detach: 刪除「訂閱者」
    • 抽象方法Notify: 通知「訂閱者」
  • 「訂閱者」(觀察者)接口ObserverInterface
    • 抽象方法Do: 自身的業務

訂單逆向流的業務下,咱們須要實現這兩個接口:

  • 具體訂單取消的動做實現「主題」接口Observable
  • 子邏輯實現「訂閱者」接口ObserverInterface

僞代碼以下:

// ------------這裏實現一個具體的「主題」------------

具體訂單取消的動做實現「主題」(被觀察者)接口`Observable`。獲得一個具體的「主題」:

- 訂單取消的動做的「主題」結構體`ObservableConcrete`
    +  成員屬性`observerList []ObserverInterface`:訂閱者列表
    +  具體方法`Attach`: 增長子邏輯
    +  具體方法`Detach`: 刪除子邏輯
    +  具體方法`Notify`: 通知子邏輯

// ------------這裏實現全部具體的「訂閱者」------------

子邏輯實現「訂閱者」接口`ObserverInterface`:

- 具體「訂閱者」也就是子邏輯`OrderStatus`
    +  實現方法`Do`: 修改訂單狀態
- 具體「訂閱者」也就是子邏輯`OrderStatusLog`
    +  實現方法`Do`: 記錄訂單狀態變動日誌
- 具體「訂閱者」也就是子邏輯`CouponRefund`
    +  實現方法`Do`: 退優惠券
- 具體「訂閱者」也就是子邏輯`PromotionRefund`
    +  實現方法`Do`: 還優惠活動資格
- 具體「訂閱者」也就是子邏輯`StockRefund`
    +  實現方法`Do`: 還庫存
- 具體「訂閱者」也就是子邏輯`GiftCardRefund`
    +  實現方法`Do`: 還禮品卡
- 具體「訂閱者」也就是子邏輯`WalletRefund`
    +  實現方法`Do`: 退錢包餘額
- 具體「訂閱者」也就是子邏輯`DeliverBillStatus`
    +  實現方法`Do`: 修改發貨單狀態
- 具體「訂閱者」也就是子邏輯`DeliverBillStatusLog`
    +  實現方法`Do`: 記錄發貨單狀態變動日誌
- 具體「訂閱者」也就是子邏輯`Refund`
    +  實現方法`Do`: 生成退款單
- 具體「訂閱者」也就是子邏輯`Invoice`
    +  實現方法`Do`: 生成發票-紅票
- 具體「訂閱者」也就是子邏輯`Email`
    +  實現方法`Do`: 發郵件
- 具體「訂閱者」也就是子邏輯`Sms`
    +  實現方法`Do`: 發短信
- 具體「訂閱者」也就是子邏輯`WechatNotify`
    +  實現方法`Do`: 發微信消息
複製代碼

同時獲得了咱們的UML圖:

代碼demo

package main

//------------------------------------------------------------
//個人代碼沒有`else`系列
//觀察者模式
//@auhtor TIGERB<https://github.com/TIGERB>
//------------------------------------------------------------

import (
	"fmt"
	"reflect"
	"runtime"
)

// Observable 被觀察者
type Observable interface {
	Attach(observer ...ObserverInterface) Observable
	Detach(observer ObserverInterface) Observable
	Notify() error
}

// ObservableConcrete 一個具體的 訂單狀態變化的被觀察者
type ObservableConcrete struct {
	observerList []ObserverInterface
}

// Attach 註冊觀察者
// @param $observer ObserverInterface 觀察者列表
func (o *ObservableConcrete) Attach(observer ...ObserverInterface) Observable {
	o.observerList = append(o.observerList, observer...)
	return o
}

// Detach 註銷觀察者
// @param $observer ObserverInterface 待註銷的觀察者
func (o *ObservableConcrete) Detach(observer ObserverInterface) Observable {
	if len(o.observerList) == 0 {
		return o
	}
	for k, observerItem := range o.observerList {
		if observer == observerItem {
			fmt.Println(runFuncName(), "註銷:", reflect.TypeOf(observer))
			o.observerList = append(o.observerList[:k], o.observerList[k+1:]...)
		}
	}
	return o
}

// Notify 通知觀察者
func (o *ObservableConcrete) Notify() (err error) {
	// code ...
	for _, observer := range o.observerList {
		if err = observer.Do(o); err != nil {
			return err
		}
	}
	return nil
}

// ObserverInterface 定義一個觀察者的接口
type ObserverInterface interface {
	// 自身的業務
	Do(o Observable) error
}

// OrderStatus 修改訂單狀態
type OrderStatus struct {
}

// Do 具體業務
func (observer *OrderStatus) Do(o Observable) (err error) {
	// code...
	fmt.Println(runFuncName(), "修改訂單狀態...")
	return
}

// OrderStatusLog 記錄訂單狀態變動日誌
type OrderStatusLog struct {
}

// Do 具體業務
func (observer *OrderStatusLog) Do(o Observable) (err error) {
	// code...
	fmt.Println(runFuncName(), "記錄訂單狀態變動日誌...")
	return
}

// CouponRefund 退優惠券
type CouponRefund struct {
}

// Do 具體業務
func (observer *CouponRefund) Do(o Observable) (err error) {
	// code...
	fmt.Println(runFuncName(), "退優惠券...")
	return
}

// PromotionRefund 還優惠活動資格
type PromotionRefund struct {
}

// Do 具體業務
func (observer *PromotionRefund) Do(o Observable) (err error) {
	// code...
	fmt.Println(runFuncName(), "還優惠活動資格...")
	return
}

// StockRefund 還庫存
type StockRefund struct {
}

// Do 具體業務
func (observer *StockRefund) Do(o Observable) (err error) {
	// code...
	fmt.Println(runFuncName(), "還庫存...")
	return
}

// GiftCardRefund 還禮品卡
type GiftCardRefund struct {
}

// Do 具體業務
func (observer *GiftCardRefund) Do(o Observable) (err error) {
	// code...
	fmt.Println(runFuncName(), "還禮品卡...")
	return
}

// WalletRefund 退錢包餘額
type WalletRefund struct {
}

// Do 具體業務
func (observer *WalletRefund) Do(o Observable) (err error) {
	// code...
	fmt.Println(runFuncName(), "退錢包餘額...")
	return
}

// DeliverBillStatus 修改發貨單狀態
type DeliverBillStatus struct {
}

// Do 具體業務
func (observer *DeliverBillStatus) Do(o Observable) (err error) {
	// code...
	fmt.Println(runFuncName(), "修改發貨單狀態...")
	return
}

// DeliverBillStatusLog 記錄發貨單狀態變動日誌
type DeliverBillStatusLog struct {
}

// Do 具體業務
func (observer *DeliverBillStatusLog) Do(o Observable) (err error) {
	// code...
	fmt.Println(runFuncName(), "記錄發貨單狀態變動日誌...")
	return
}

// Refund 生成退款單
type Refund struct {
}

// Do 具體業務
func (observer *Refund) Do(o Observable) (err error) {
	// code...
	fmt.Println(runFuncName(), "生成退款單...")
	return
}

// Invoice 生成發票-紅票
type Invoice struct {
}

// Do 具體業務
func (observer *Invoice) Do(o Observable) (err error) {
	// code...
	fmt.Println(runFuncName(), "生成發票-紅票...")
	return
}

// Email 發郵件
type Email struct {
}

// Do 具體業務
func (observer *Email) Do(o Observable) (err error) {
	// code...
	fmt.Println(runFuncName(), "發郵件...")
	return
}

// Sms 發短信
type Sms struct {
}

// Do 具體業務
func (observer *Sms) Do(o Observable) (err error) {
	// code...
	fmt.Println(runFuncName(), "發短信...")
	return
}

// WechatNotify 發微信消息
type WechatNotify struct {
}

// Do 具體業務
func (observer *WechatNotify) Do(o Observable) (err error) {
	// code...
	fmt.Println(runFuncName(), "發微信消息...")
	return
}

// 客戶端調用
func main() {

	// 建立 未支付取消訂單 「主題」
	fmt.Println("----------------------- 未支付取消訂單 「主題」")
	orderUnPaidCancelSubject := &ObservableConcrete{}
	orderUnPaidCancelSubject.Attach(
		&OrderStatus{},
		&OrderStatusLog{},
		&CouponRefund{},
		&PromotionRefund{},
		&StockRefund{},
	)
	orderUnPaidCancelSubject.Notify()

	// 建立 超時關單 「主題」
	fmt.Println("----------------------- 超時關單 「主題」")
	orderOverTimeSubject := &ObservableConcrete{}
	orderOverTimeSubject.Attach(
		&OrderStatus{},
		&OrderStatusLog{},
		&CouponRefund{},
		&PromotionRefund{},
		&StockRefund{},
		&Email{},
		&Sms{},
		&WechatNotify{},
	)
	orderOverTimeSubject.Notify()

	// 建立 已支付取消訂單 「主題」
	fmt.Println("----------------------- 已支付取消訂單 「主題」")
	orderPaidCancelSubject := &ObservableConcrete{}
	orderPaidCancelSubject.Attach(
		&OrderStatus{},
		&OrderStatusLog{},
		&CouponRefund{},
		&PromotionRefund{},
		&StockRefund{},
		&GiftCardRefund{},
		&WalletRefund{},
		&Refund{},
		&Invoice{},
		&Email{},
		&Sms{},
		&WechatNotify{},
	)
	orderPaidCancelSubject.Notify()

	// 建立 取消發貨單 「主題」
	fmt.Println("----------------------- 取消發貨單 「主題」")
	deliverBillCancelSubject := &ObservableConcrete{}
	deliverBillCancelSubject.Attach(
		&OrderStatus{},
		&OrderStatusLog{},
		&DeliverBillStatus{},
		&DeliverBillStatusLog{},
		&StockRefund{},
		&GiftCardRefund{},
		&WalletRefund{},
		&Refund{},
		&Invoice{},
		&Email{},
		&Sms{},
		&WechatNotify{},
	)
	deliverBillCancelSubject.Notify()

	// 建立 拒收 「主題」
	fmt.Println("----------------------- 拒收 「主題」")
	deliverBillRejectSubject := &ObservableConcrete{}
	deliverBillRejectSubject.Attach(
		&OrderStatus{},
		&OrderStatusLog{},
		&DeliverBillStatus{},
		&DeliverBillStatusLog{},
		&StockRefund{},
		&GiftCardRefund{},
		&WalletRefund{},
		&Refund{},
		&Invoice{},
		&Email{},
		&Sms{},
		&WechatNotify{},
	)
	deliverBillRejectSubject.Notify()

	// 將來能夠快速的根據業務的變化 建立新的主題 從而快速構建新的業務接口
	fmt.Println("----------------------- 將來的擴展...")

}

// 獲取正在運行的函數名
func runFuncName() string {
	pc := make([]uintptr, 1)
	runtime.Callers(2, pc)
	f := runtime.FuncForPC(pc[0])
	return f.Name()
}
複製代碼

代碼運行結果:

[Running] go run "../easy-tips/go/src/patterns/observer/observer.go"
----------------------- 未支付取消訂單 「主題」
main.(*OrderStatus).Do 修改訂單狀態...
main.(*OrderStatusLog).Do 記錄訂單狀態變動日誌...
main.(*CouponRefund).Do 退優惠券...
main.(*PromotionRefund).Do 還優惠活動資格...
main.(*StockRefund).Do 還庫存...
----------------------- 超時關單 「主題」
main.(*OrderStatus).Do 修改訂單狀態...
main.(*OrderStatusLog).Do 記錄訂單狀態變動日誌...
main.(*CouponRefund).Do 退優惠券...
main.(*PromotionRefund).Do 還優惠活動資格...
main.(*StockRefund).Do 還庫存...
main.(*Email).Do 發郵件...
main.(*Sms).Do 發短信...
main.(*WechatNotify).Do 發微信消息...
----------------------- 已支付取消訂單 「主題」
main.(*OrderStatus).Do 修改訂單狀態...
main.(*OrderStatusLog).Do 記錄訂單狀態變動日誌...
main.(*CouponRefund).Do 退優惠券...
main.(*PromotionRefund).Do 還優惠活動資格...
main.(*StockRefund).Do 還庫存...
main.(*GiftCardRefund).Do 還禮品卡...
main.(*WalletRefund).Do 退錢包餘額...
main.(*Refund).Do 生成退款單...
main.(*Invoice).Do 生成發票-紅票...
main.(*Email).Do 發郵件...
main.(*Sms).Do 發短信...
main.(*WechatNotify).Do 發微信消息...
----------------------- 取消發貨單 「主題」
main.(*OrderStatus).Do 修改訂單狀態...
main.(*OrderStatusLog).Do 記錄訂單狀態變動日誌...
main.(*DeliverBillStatus).Do 修改發貨單狀態...
main.(*DeliverBillStatusLog).Do 記錄發貨單狀態變動日誌...
main.(*StockRefund).Do 還庫存...
main.(*GiftCardRefund).Do 還禮品卡...
main.(*WalletRefund).Do 退錢包餘額...
main.(*Refund).Do 生成退款單...
main.(*Invoice).Do 生成發票-紅票...
main.(*Email).Do 發郵件...
main.(*Sms).Do 發短信...
main.(*WechatNotify).Do 發微信消息...
----------------------- 拒收 「主題」
main.(*OrderStatus).Do 修改訂單狀態...
main.(*OrderStatusLog).Do 記錄訂單狀態變動日誌...
main.(*DeliverBillStatus).Do 修改發貨單狀態...
main.(*DeliverBillStatusLog).Do 記錄發貨單狀態變動日誌...
main.(*StockRefund).Do 還庫存...
main.(*GiftCardRefund).Do 還禮品卡...
main.(*WalletRefund).Do 退錢包餘額...
main.(*Refund).Do 生成退款單...
main.(*Invoice).Do 生成發票-紅票...
main.(*Email).Do 發郵件...
main.(*Sms).Do 發短信...
main.(*WechatNotify).Do 發微信消息...

複製代碼

結語

最後總結下,「觀察者模式」抽象過程的核心是:

  • 被依賴的「主題」
  • 被通知的「訂閱者」
  • 「訂閱者」按需訂閱「主題」
  • 「主題」變化通知「訂閱者」
特別說明:
1. 個人代碼沒有`else`,只是一個在代碼合理設計的狀況下天然而然無限接近或者達到的結果,並非一個硬性的目標,務必較真。
2. 本系列的一些設計模式的概念可能和原概念存在差別,由於會結合實際使用,取其精華,適當改變,靈活使用。
3. 觀察者模式與訂閱通知實際仍是有差別,本文均加上了雙引號。訂閱通知:訂閱方不是直接依賴主題方(聯想下mq等消息中間件的使用);而觀察者模式:觀察者是直接依賴了被觀察者,從上面的代碼咱們也能夠清晰的看出來這個差別。
複製代碼

文章列表

個人代碼沒有else系列 更多文章 點擊此處查看

相關文章
相關標籤/搜索