实践 GoF 的 23 种设计模式:命令模式
本文分享自华为云社区《【Go实现】实践GoF的23种设计模式:命令模式》,作者:元闰子。
简介
现在的软件系统往往是分层设计。在业务层执行一次请求时,我们很清楚请求的上下文,包括,请求是做什么的、参数有哪些、请求的接收者是谁、返回值是怎样的。相反,基础设施层并不需要完全清楚业务上下文,它只需知道请求的接收者是谁即可,否则就耦合过深了。
因此,我们需要对请求进行抽象,将上下文信息封装到请求对象里,这其实就是命令模式,而该请求对象就是 Command。
GoF 对命令模式(Command Pattern)的定义如下:
Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
也即,命令模式可将请求转换为一个包含与请求相关的所有信息的对象, 它能将请求参数化、延迟执行、实现 Undo / Redo 操作等。
上述的请求是广义上的概念,可以是网络请求,也可以是函数调用,更通用地,指一个动作。
命令模式主要包含 3 种角色:
Command,命令,是对请求的抽象。具体的命令实现时,通常会引用 Receiver。
Invoker,请求的发起发起方,它并不清楚 Command 和 Receiver 的实现细节,只管调用命令的接口。
Receiver,请求的接收方。
命令模式,一方面,能够使得 Invoker 与 Receiver 消除彼此之间的耦合,让对象之间的调用关系更加灵活;另一方面,能够很方便地实现延迟执行、Undo、Redo 等操作,因此被广泛应用在软件设计中。
UML 结构
场景上下文
在 简单的分布式应用系统(示例代码工程)中,db 模块用来存储服务注册信息和系统监控数据。其中,服务注册信息拆成了 profiles
和 regions
两个表,在服务发现的业务逻辑中,通常需要同时操作两个表,为了避免两个表数据不一致的问题,db 模块需要提供事务功能:
事务的核心功能之一是,当其中某个语句执行失败时,之前已执行成功的语句能够回滚,而使用命令模式能够很方便地实现该功能。
代码实现
客户端可以这么使用:
总结实现命令模式的几个关键点:
定义命令抽象接口,本例子中为
Command
接口。在命令抽象接口中声明执行命令的方法,本例子中为
Exec
方法。如果要实现撤销功能,还需要为命令对象定义
Undo
方法,在操作回滚时调用。定义
Invoker
对象,本例子中为Transaction
对象。在 Invoker 对象持有 Command 的引用,本例子为
Command
的切片cmds
。为 Invoker 对象定义 Call 方法,用于执行具体的命令,在方法内调用 Command 的执行方法 ,本例子中为
Transaction.Commit
方法。如果要实现撤销功能,还要在回滚方法中,调用已执行命令的
Undo
方法,本例子中为cmdHistory.rollback
方法。定义具体的命令类,实现
Command
接口,本例子中为InsertCmd
、UpdateCmd
、DeleteCmd
。命令通常持有接收者的引用,以便在执行方法中与接收者交互。本例子中,Receiver 为
Db
对象。最后,在命令对象执行方法中,调用 Receiver 的 Action 方法,本例子中, Receiver 的 Action 方法为
db.Insert
方法。
值得注意的是,本例子中 Transaction
对象在 Transaction.Exec
方法中只是将 Command
保存在队列中,只有当调用 Transaction.Commit
方法时才延迟执行相应的命令。
扩展
os/exec 中的命令模式
Go 标准库的 os/exec
包也用到了命令模式。
在上述例子中,我们通过 exec.Command
方法将一个 shell 命令转换成一个命令对象 exec.Cmd
,其中的 Cmd.Run()
方法即是命令执行方法;而 main()
函数,对应到命令模式中的 Invoker;Receiver 则是操作系统执行 shell 命令的具体进程,从 exec.Cmd
的源码中可以看到:
CQRS 架构
CQRS 架构,全称为 Command Query Responsibility Segregation,命令查询职责隔离架构。CQRS 架构是微服务架构模式中的一种,它利用事件(命令)来维护从多个服务复制数据的只读视图,通过读写分离思想,提升微服务架构下查询的性能。
CQRS 架构可分为 命令端 和 查询端,其中命令端负责数据的更新;查询端负责数据的查询。命令端的写数据库在数据更新时,会向查询端的只读数据库发送一个同步数据的事件,保证数据的最终一致性。
其中的命令端,就使用到了命令模式的思想,将数据更新请求封装成命令,异步更新到写数据库中。
典型应用场景
事务模式。事务模式下往往需要 Undo 操作,使用命令模式实现起来很方便。
远程执行。Go 标准库下的
exec.Cmd
、http.Client
都属于该类型,将请求封装成命令来执行。CQRS 架构。微服务架构模式中的一种,通过命令模式来实现数据的异步更新。
延迟执行。当你希望一个操作能够延迟执行时,通常会将它封装成命令,然后放到一个队列中。
优缺点
优点
符合单一职责原则。在命令模式下,每个命令都是职责单一、松耦合的;当然也可以通过组合的方式,将多个简单的命令组合成一个负责的命令。
可以很方便地实现操作的延迟执行、回滚、重做等。
在分布式架构下,命令模式能够方便地实现异步的数据更新、方法调用等,提升性能。
缺点
命令模式下,调用往往是异步的,而异步会导致系统变得复杂,问题出现时不好定位解决。
随着业务越来越复杂,命令对象也会增多,代码会变得更难维护。
与其他模式的关联
在实现 Undo/Redo 操作时,你通常需要同时使用 命令模式 和 备忘录模式。
另外,命令模式 也常常和 观察者模式 一起出现,比如在 CQRS 架构中,当命令端更新数据库后,写数据库就会通过事件将数据同步到读数据库上,这里就用到了 观察者模式。
文章配图
可以在 用Keynote画出手绘风格的配图 中找到文章的绘图方法。
参考
[1] 【Go实现】实践GoF的23种设计模式:SOLID原则, 元闰子
[2] 【Go实现】实践GoF的23种设计模式:观察者模式, 元闰子
[3] Design Patterns, Chapter 5. Behavioral Patterns, GoF
[4] 命令模式, refactoringguru.cn
[5] The command pattern in Go, rolandjitsu
[6] CQRS 模式, microsoft azure
[7] CQRS Design Pattern in Microservices Architectures, Mehmet Ozkaya
版权声明: 本文为 InfoQ 作者【华为云开发者联盟】的原创文章。
原文链接:【http://xie.infoq.cn/article/89bfe54855b1e667f0ede294c】。文章转载请联系作者。
评论