Подпишитесь на уведомления | Go Design Patterns в действии

Go Шаблоны проектирования

Что ж, серия боевых шаблонов дизайна Go, серия golang, фактически используемая бизнесом шаблонов проектирования.

предисловие

Эта серия в основном рассказывает о том, как использовать шаблоны проектирования в наших реальных бизнес-сценариях.

Эта серия статей в основном имеет следующую структуру:

  • Что такое «Шаблон проектирования XX»?
  • В каких реальных бизнес-сценариях можно использовать «шаблон проектирования XX»?
  • Как использовать «Шаблон дизайна XX»?

Хотя заголовок этой статьи называется «Уведомление о подписке», в ней в основном рассказывается, как «шаблон наблюдателя» используется в реальных бизнес-сценариях. Вы что-то не понимаете? пожалуйста, объясни:

  • По одной причине «шаблон наблюдателя» на самом деле выглядит как «уведомление о подписке».
  • Причина вторая: «уведомления о подписке» легче понять

Что такое «паттерн наблюдателя»?

Наблюдатель наблюдает за наблюдаемым, а наблюдаемое сообщает наблюдателю

Переводим понятие «шаблон наблюдателя» на «уведомление о подписке», результат:

"Подписчики подписываются на темы, темы уведомляют подписчиков"

Так проще понять, давайте разберем это предложение и получим:

  • два объекта
    • Наблюдаемый -> Тема
    • Наблюдатель -> Подписчик
  • два действия
    • Подписка -> Подписчикподпискатема
    • Уведомление -> Тема измененаУведомлениеподписчик

Преимущества шаблона наблюдателя:

  • Высокая согласованность -> изменения в разных бизнес-кодах не влияют друг на друга
  • Многоразовый -> Новый бизнес (то есть новый подписчик) подписывается на разные интерфейсы (тема, какой тут интерфейс)
  • Чрезвычайно легко расширяется -> новый интерфейс (то есть новые темы), новые услуги (то есть новые подписчики);

На самом деле, говоря прямо, это преимущество использования механизма сообщений MQ для разделения бизнеса в распределенной архитектуре.Это легко понять, если вы так думаете.

В каких реальных бизнес-сценариях можно использовать «шаблон наблюдателя»?

Все бизнес-сценарии, которые необходимо уведомлять об изменениях

Подробно: пока происходят определенные изменения, необходимо уведомлять бизнес-сценарии конкретных вещей, которые зависят от этих изменений.

В каких реальных бизнес-сценариях мы можем использовать «шаблон наблюдателя»?

Например, обратный поток заказов, то есть различные операции отмены после установления заказа (послепродажное обслуживание в данной статье не рассматривается), в основном включают в себя следующие виды отмены:

Тип отмены заказа
Отмененный заказ не оплачен
Порядок закрытия сверхурочной работы
Заплатил за отмену заказа
Отменить счет
отклонять

запуск этихОтменить операциюдолжны выполнять различные подоперации, очевидно, разныеОтменить операциюЗадействованные подоперации пересекаются. Во-вторых, подоперация оплаченных и отмененных заказов должна быть наиболее полной из всех типов отмены заказов, и можно использовать другие типы повторного использования кода, кроме разделения на фрагменты функций, есть ли лучший способ их инкапсулировать? Ответ: "Режим наблюдателя".

Далее, давайте проанализируем бизнес обратного потока при размещении заказа.Изменятьипостоянный:

  • Изменять
    • Добавить тип отмены
    • Добавить подоперацию
    • Изменить логику подоперации
    • Отменить соответствие между типами и подоперациями
  • постоянный
    • Тип отмены, который уже существует
    • Существующие подоперации (из внешнего мира)

Как использовать «Режим наблюдателя»?

Что касается того, как его использовать, вполне возможно скопировать четыре шага, которые я суммировал при использовании шаблонов проектирования:

  • Деловой кардинг
  • Блок-схема бизнеса
  • моделирование кода
  • демонстрация кода

Деловой кардинг

注:本文于单体架构背景探讨业务的实现过程,简单容易理解。

Первый шаг — разобрать все существующие подоперации обратного бизнеса следующим образом:

все поддействия
Изменить статус заказа
Запись журнала изменения статуса заказа
Возврат купона
Право на продвижение по службе
все еще в наличии
вернуть подарочную карту
Вернуть баланс кошелька
Изменить статус счета
Запись журнала изменения статуса счета-фактуры
Создать возврат
Создать счет - красный билет
отправить электронное письмо
текстовые сообщения
Отправить сообщение WeChat

Второй шаг — найти взаимосвязь между различными типами отмены заказа и этими подоперациями следующим образом:

Тип отмены заказа («Тема») (наблюдается) Suberperations («Подписчики») (наблюдатели)
Отменить неоплаченный заказ -
- Изменить статус заказа
- Запись журнала изменения статуса заказа
- Возврат купона
- Право на продвижение по службе
- все еще в наличии
Порядок закрытия сверхурочной работы -
- Изменить статус заказа
- Запись журнала изменения статуса заказа
- Возврат купона
- Право на продвижение по службе
- все еще в наличии
- отправить электронное письмо
- текстовые сообщения
- Отправить сообщение WeChat
Оплачен отмененный заказ (счет не создан) -
- Изменить статус заказа
- Запись журнала изменения статуса заказа
- Квалификация для льготной деятельности (в зависимости от ситуации)
- все еще в наличии
- вернуть подарочную карту
- Вернуть баланс кошелька
- Создать квитанцию ​​о возврате
- Создать счет - красный билет
- отправить электронное письмо
- текстовые сообщения
- Отправить сообщение WeChat
Отменить счет-фактуру (не отправлено) -
- Изменить статус заказа
- Запись журнала изменения статуса заказа
- Изменить статус счета
- Запись журнала изменения статуса счета-фактуры
- все еще в наличии
- вернуть подарочную карту
- Вернуть баланс кошелька
- Создать квитанцию ​​о возврате
- Создать счет - красный билет
- отправить электронное письмо
- текстовые сообщения
- Отправить сообщение WeChat
отклонять -
- Изменить статус заказа
- Запись журнала изменения статуса заказа
- Изменить статус счета
- Запись журнала изменения статуса счета-фактуры
- все еще в наличии
- вернуть подарочную карту
- Вернуть баланс кошелька
- Создать квитанцию ​​о возврате
- Создать счет - красный билет
- отправить электронное письмо
- текстовые сообщения
- Отправить сообщение WeChat

Примечание. Этот процесс не обязательно является полностью точным и всеобъемлющим.

в заключении:

  • Подоперации разных типов отмены ордеров имеют пересечение, и подоперации могут использоваться повторно.
  • Подоперации можно рассматривать как «подписчиков» (они же наблюдатели).
  • Типы отмены ордеров можно рассматривать как «субъекты» (они же наблюдаемые).
  • различные подоперации («подписчики») (наблюдатели)подпискаТип отмены заказа («Тема») (наблюдается)
  • Тип отмены заказа («Тема») (наблюдается)УведомлениеПодоперации («подписчики») (наблюдатели)

Блок-схема бизнеса

Мы получили следующую блок-схему бизнес-процесса, объединив текст бизнес-процесса:

注:本文于单体架构背景探讨业务的实现过程,简单容易理解。

моделирование кода

Ядром «Обозревателя» являются два интерфейса:

  • Интерфейс «субъект» (наблюдаемый)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等消息中间件的使用);而观察者模式:观察者是直接依赖了被观察者,从上面的代码我们也可以清晰的看出来这个差异。

Список статей

Go Design Pattern Combat Series Больше статей нажмите здесь, чтобы просмотреть