命令模式是行为设计模式。
GoF定义:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作
优点:调用者与实现者的解耦
场景
会员系统实现,订阅到商品到货通知、订阅商品降价通知。
订阅到商品到货通知: 大致的流程是,当商品缺货时候,用户可以订阅到货通知,当商品有库存时,发送消息通知用户。
订阅商品降价通知:大致的流程是,当用户觉得商品太贵时,可以订阅降价通知,当商品的价格达到用户期望价格时,发送消息通知用户。
流程实现
实现这个两个功能并不难。
大致的技术实现流程如下,具体细节不做展开,用这两个例子是为了介绍引入命令模式。
订阅到商品到货通知: 到货通知订阅表,记录用户的订阅记录。
发送记录表,记录消息的发送记录。
订阅时,到货通知订阅表插入数据。消息发送时,发送记录表插入数据。
关注商品的库存状态,一般是使用订阅库存消息,当商品有库存时,遍历订阅表,发送消息,并且在数据库中记录下发送消息的记录。
订阅商品降价通知:
降价通知订阅表。记录用户的订阅记录。
发送记录表,记录消息的发送记录。 订阅时,降价通知订阅表插入数据。消息发送时,发送记录表插入数据。
关注商品的价格状态,一般是使用订阅价格变动消息,当商品价格变化时,遍历订阅表,判断价格是否达到用户的预期,价格达到预期,发送消息,并且在数据库中记录下发送消息的记录。
分析上面的两个业务
调用者:负责扫描订阅表,把符合记录的订阅信息推送给执行者;依赖订阅记录的抽象接口。
执行者:负责自己的业务职责,组装数据,发送消息;需要接收订阅记录传递的数据(需要发送的消息)。
订阅记录:需要保存执行的触达方式。
具体实现:
调用者
go
// ISubscribeRecord 抽象出来的订阅记录
type ISubscribeRecord interface {
// 发送消息
ExecuteSend()
}
// Invoke 调用者
type Invoke struct {
// 满足条件的订阅消息
subscribeRecords []ISubscribeRecord
}
func NewInvoke() *Invoke {
return &Invoke{}
}
// SetSubscribeRecord 添加满足条件的订阅消息
func (i *Invoke) SetSubscribeRecord(subscribeRecord ISubscribeRecord) {
i.subscribeRecords = append(i.subscribeRecords, subscribeRecord)
}
// Exec 执行数据的发送
func (i *Invoke) Exec() {
if len(i.subscribeRecords) == 0 {
return
}
for _, subscribeRecord := range i.subscribeRecords {
subscribeRecord.ExecuteSend()
}
}
到货通知、降价通知的调用者都可以共用这段代码。
这里的订阅记录是个接口,这样可以很方便的进行数据的发送
实现者
go
// Notify 发送方式
type Notify struct {
sendWay string
}
func NewNotify(sendWay string) *Notify {
return &Notify{sendWay: sendWay}
}
func (n *Notify) Send(productName string) {
fmt.Printf("%v, 发送方式: %v, 命令执行了!\n", productName, n.sendWay)
}
这部分只关心消息的发送,按照指定的发送方式,触达用户
订阅记录
到货通知
go
// AlertSockProductSubscribeRecord 到货通知订阅记录
type AlertSockProductSubscribeRecord struct {
ProductName string
Notify *Notify
}
func NewAlertSockProductSubscribeRecord(notify *Notify, productName string) ISubscribeRecord {
return &AlertSockProductSubscribeRecord{
Notify: notify,
ProductName: productName + "到货了",
}
}
func (r *AlertSockProductSubscribeRecord) ExecuteSend() {
r.Notify.Send(r.ProductName)
}
降价提醒
go
// ReducePriceProductSubscribeRecord 降价通知订阅记录
type ReducePriceProductSubscribeRecord struct {
ProductName string
Notify *Notify
}
func NewReducePriceProductSubscribeRecord(notify *Notify, productName string) ISubscribeRecord {
return &ReducePriceProductSubscribeRecord{
Notify: notify,
ProductName: productName + "降价了",
}
}
func (r *ReducePriceProductSubscribeRecord) ExecuteSend() {
// 可以加入一些其他的判断。晚上时段,不打扰用户,可以把发送记录标记上暂时不发送。
// 判断当前时间是否符合发送时间
// 不符合,记录到数据库
// 符合,立即执行发送
r.Notify.Send(r.ProductName)
}
到货通知订阅记录、降价提醒订阅记录 持有触达方式(执行者)的一个引用。
这样可以知道该订阅记录应该用什么方式触达。
完整代码:
go
// 命令模式
import (
"fmt"
)
type ISubscribeRecord interface {
ExecuteSend()
}
// Invoke 调用者
type Invoke struct {
subscribeRecords []ISubscribeRecord
}
func NewInvoke() *Invoke {
return &Invoke{}
}
// SetSubscribeRecord 添加满足条件的订阅消息
func (i *Invoke) SetSubscribeRecord(subscribeRecord ISubscribeRecord) {
i.subscribeRecords = append(i.subscribeRecords, subscribeRecord)
}
func (i *Invoke) Exec() {
if len(i.subscribeRecords) == 0 {
return
}
for _, subscribeRecord := range i.subscribeRecords {
subscribeRecord.ExecuteSend()
}
}
// Notify 发送消息 实现者
type Notify struct {
sendWay string
}
func NewNotify(sendWay string) *Notify {
return &Notify{sendWay: sendWay}
}
func (n *Notify) Send(productName string) {
fmt.Printf("%v, 发送方式: %v, 命令执行了!\n", productName, n.sendWay)
}
// ------------------降价通知业务逻辑----------------------
// ReducePriceProductSubscribeRecord 降价通知订阅记录
type ReducePriceProductSubscribeRecord struct {
ProductName string
Notify *Notify
}
func NewReducePriceProductSubscribeRecord(notify *Notify, productName string) ISubscribeRecord {
return &ReducePriceProductSubscribeRecord{
Notify: notify,
ProductName: productName + "降价了",
}
}
func (r *ReducePriceProductSubscribeRecord) ExecuteSend() {
// 可以加入一些其他的判断。晚上时段,不打扰用户,可以把发送记录标记上暂时不发送。
// 判断当前时间是否符合发送时间
// 不符合,记录到数据库
// 符合,立即执行发送
r.Notify.Send(r.ProductName)
}
// ------------------到货通知业务逻辑----------------------
// AlertSockProductSubscribeRecord 到货通知订阅记录
type AlertSockProductSubscribeRecord struct {
ProductName string
Notify *Notify
}
func NewAlertSockProductSubscribeRecord(notify *Notify, productName string) ISubscribeRecord {
return &AlertSockProductSubscribeRecord{
Notify: notify,
ProductName: productName + "到货了",
}
}
func (r *AlertSockProductSubscribeRecord) ExecuteSend() {
r.Notify.Send(r.ProductName)
}
// 调用者 和 实现者解耦
func main() {
// 执行者
notifySms := NewNotify("短信")
notifyApp := NewNotify("APP推送")
notifyMniProgramSubscribe := NewNotify("微信小程序订阅消息")
// 商品名称
productName := "【设计模式:可复用面向对象软件的基础】"
// 订阅降价通知
reducePriceProduct1 := NewReducePriceProductSubscribeRecord(notifySms, productName)
reducePriceProduct2 := NewReducePriceProductSubscribeRecord(notifyApp, productName)
reducePriceProduct3 := NewReducePriceProductSubscribeRecord(notifyMniProgramSubscribe, productName)
// 下面三个步骤省略,不是这次的介绍的重点
// 1.记录数据库操作...
// 2.遍历数据库操作...
// 3.找出符合条件的记录...
// 降价通知调用者
reduceInvoke := NewInvoke()
reduceInvoke.SetSubscribeRecord(reducePriceProduct1)
reduceInvoke.SetSubscribeRecord(reducePriceProduct2)
reduceInvoke.SetSubscribeRecord(reducePriceProduct3)
reduceInvoke.Exec()
// 订阅到货通知
alertStockPriceProduct1 := NewAlertSockProductSubscribeRecord(notifySms, productName)
alertStockPriceProduct2 := NewAlertSockProductSubscribeRecord(notifyApp, productName)
// 下面三个步骤省略,不是这次的介绍的重点
// 1.记录数据库操作...
// 2.遍历数据库操作...
// 3.找出符合条件的记录...
// 到货通知调用者
alertInvoke := NewInvoke()
alertInvoke.SetSubscribeRecord(alertStockPriceProduct1)
alertInvoke.SetSubscribeRecord(alertStockPriceProduct2)
alertInvoke.Exec()
}
总结
怎么样?看了上面的代码有什么感觉。是不是觉得可以非常方便的扩展新的业务。
比如来了一个新的需求:
小程序端,需要订阅促销活动即将开始的通知、订阅促销活动即将结束的通知。
这两个微信小程序订阅消息也可以仿照上述的逻辑,快速的实现。
不用调整大量的代码,体现了"对扩展开放,对修改关闭 "的设计原则。
消息发送的执行者,消息的触达方式也有很多,比如:短信、邮件、APP推送、微信小程序订阅消息、微信公众号模板消息。这些触达方式的实现也有很多设计模式可以实现解耦,比如上次介绍的抽象工厂
再举个例子,很多教材会用饭馆来举例,这里沿用这个示例。
1.作为顾客的我们是命令的下达者。
2.服务员是这个命令的接收者。
3.菜单是这个实际的命令。
4.厨师是这个命令的执行者。
业务场景:
当你要修改菜单的时候,只需要和服务员说就好了,她会转达给厨师,也就是说,我们实现了顾客和厨师的解耦。也就是调用者与实现者的解耦。
命令模式能够做到的是让一个命令接收者实现多个命令(服务员下单、拿酒水、上菜),或者把一条命令转达给多个实现者(热菜厨师、凉菜厨师、主食师傅)。