嗯,個人代碼沒有
else
系列,一個設計模式業務真實使用的golang系列。git
本系列主要分享,如何在咱們的真實業務場景中使用設計模式。github
本系列文章主要採用以下結構:golang
雖然本文的題目叫作「訂閱通知」,可是呢,本文卻主要介紹「觀察者模式」如何在真實業務場景中使用。是否是有些不理解?解釋下:設計模式
觀察者觀察被觀察者,被觀察者通知觀察者bash
咱們用「訂閱通知」翻譯下「觀察者模式」的概念,結果:微信
「訂閱者訂閱主題,主題通知訂閱者」架構
是否是容易理解多了,咱們再來拆解下這句話,獲得:app
觀察者模式的優點:分佈式
其實說白了,就是分佈式架構中使用消息機制MQ解耦業務的優點,是否是這麼一想很容易理解了。函數
全部發生變動,須要通知的業務場景
詳細說:只要發生了某些變化,須要通知依賴了這些變化的具體事物的業務場景。
咱們有哪些真實業務場景能夠用「觀察者模式」呢?
好比,訂單逆向流,也就是訂單成立以後的各類取消操做(本文不討論售後),主要有以下取消類型:
訂單取消類型 |
---|
未支付取消訂單 |
超時關單 |
已支付取消訂單 |
取消發貨單 |
拒收 |
在觸發這些取消操做都要進行各類各樣的子操做,顯而易見不一樣的取消操做所涉及的子操做是存在交集的。其次,已支付取消訂單的子操做應該是全部訂單取消類型最全的,其餘類型的複用代碼便可,除了分裝成函數片斷,還有什麼更好的封裝方式嗎?答案:「觀察者模式」。
接着咱們來分析下訂單逆向流業務中的變與不變:
關於怎麼用,徹底能夠生搬硬套我總結的使用設計模式的四個步驟:
注:本文於單體架構背景探討業務的實現過程,簡單容易理解。
複製代碼
第一步,梳理出全部存在的的逆向業務的子操做,以下:
全部子操做 |
---|
修改訂單狀態 |
記錄訂單狀態變動日誌 |
退優惠券 |
還優惠活動資格 |
還庫存 |
還禮品卡 |
退錢包餘額 |
修改發貨單狀態 |
記錄發貨單狀態變動日誌 |
生成退款單 |
生成發票-紅票 |
發郵件 |
發短信 |
發微信消息 |
第二步,找到不一樣訂單取消類型和這些子操做的關係,以下:
訂單取消類型(「主題」)(被觀察者) | 子操做(「訂閱者」)(觀察者) |
---|---|
取消未支付訂單 | - |
- | 修改訂單狀態 |
- | 記錄訂單狀態變動日誌 |
- | 退優惠券 |
- | 還優惠活動資格 |
- | 還庫存 |
超時關單 | - |
- | 修改訂單狀態 |
- | 記錄訂單狀態變動日誌 |
- | 退優惠券 |
- | 還優惠活動資格 |
- | 還庫存 |
- | 發郵件 |
- | 發短信 |
- | 發微信消息 |
已支付取消訂單(未生成發貨單) | - |
- | 修改訂單狀態 |
- | 記錄訂單狀態變動日誌 |
- | 還優惠活動資格(看狀況) |
- | 還庫存 |
- | 還禮品卡 |
- | 退錢包餘額 |
- | 生成退款單 |
- | 生成發票-紅票 |
- | 發郵件 |
- | 發短信 |
- | 發微信消息 |
取消發貨單(未發貨) | - |
- | 修改訂單狀態 |
- | 記錄訂單狀態變動日誌 |
- | 修改發貨單狀態 |
- | 記錄發貨單狀態變動日誌 |
- | 還庫存 |
- | 還禮品卡 |
- | 退錢包餘額 |
- | 生成退款單 |
- | 生成發票-紅票 |
- | 發郵件 |
- | 發短信 |
- | 發微信消息 |
拒收 | - |
- | 修改訂單狀態 |
- | 記錄訂單狀態變動日誌 |
- | 修改發貨單狀態 |
- | 記錄發貨單狀態變動日誌 |
- | 還庫存 |
- | 還禮品卡 |
- | 退錢包餘額 |
- | 生成退款單 |
- | 生成發票-紅票 |
- | 發郵件 |
- | 發短信 |
- | 發微信消息 |
注:流程不必定徹底準確、全面。
結論:
咱們經過梳理的文本業務流程獲得了以下的業務流程圖:
注:本文於單體架構背景探討業務的實現過程,簡單容易理解。
複製代碼
「觀察者模式」的核心是兩個接口:
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圖:
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等消息中間件的使用);而觀察者模式:觀察者是直接依賴了被觀察者,從上面的代碼咱們也能夠清晰的看出來這個差別。
複製代碼