命令模式可将请求转换为一个包含与请求相关的所有信息的独立对象,这个独立对象实现了调用者与接收者之间的解耦, 命令模式最大的杀手锏是它能非常轻松的实现撤销操作。
命令模式的核心概念
在讲解命令模式之前,先列出命令模式中所涉及的一些核心概念:
1.发送者,也称触发者。
2.命令接口,通常仅声明一个执行命令的方法。
3.具体命令,实现各种类型的请求,但是它并不实现业务逻辑,而是委派给接收者。
4.接收者,真正干活的,实现业务逻辑。
5.客户端,创建并配置具体命令对象,并让命令对象与发送者关联。
学习设计模式最大的困难在于极难找到一个合适的业务场景集合实际情况讲解, 其次,人们总喜欢用自己习惯的方法解决问题,对设计模式总是提出各种质疑。
本文从一个很贴近生活的例子逐步拆解命令模式。
设计一个文本编辑器
现在想设计一个文本编辑器,目前第一步,要实现创建新文件这个功能, 提供3种创建文件的途径:
1.通过菜单创建
2.通过按钮创建
3.通过快捷键创建
3种创建途径,本质上都是在执行同一个动作,创建文件。
显然,不管这3个途径的代码在哪里去编写,只写一个创建文件的函数 create_file
,让他们3个去调用。
import os
from abc import ABC, abstractmethod
def create_file(filename):
with open(filename, 'w')as f:
pass
class AbcCommand(ABC):
@abstractmethod
def execute(self):
pass
class ABCReceiver(ABC):
@abstractmethod
def create_file(self, filename):
pass
class NewFileReceiver(ABCReceiver):
def create_file(self, filename):
with open(filename, 'w')as f:
pass
class NewFileCommand(AbcCommand):
def __init__(self, reciver, new_file_name):
self.reciver = reciver
self.new_file_name = new_file_name
def execute(self):
self.reciver.create_file(self.new_file_name)
NewFileCommand
是 AbcCommand
的子类,
它的初始化参数里包括了具体的业务接收者和完成这个命令的最关键的参数,新文件的名字。
这里必须解释一下,为什么不在 NewFileCommand
里实现新建文件的业务逻辑,
而是非要引入 reciver
,让 reciver
去完成呢,有必要搞的这么麻烦么?
这样做,主要是为了解耦,为了分层,为了应对变化,目前实现的新建文件的功能,
是在本地电脑上新建,如果将来出现一个新的功能,在云端新建文件,
那么就可以实现一个 CloudNewFileReceiver
。
class CloudNewFileReceiver(ABCReceiver):
def create_file(self, filename):
pass
将 CloudNewFileReceiver
的实例对象传入 NewFileCommand
的初始化函数里,
就能够实现在云端新建文件的功能,对于 NewFileCommand
而言,它并不关心传入的 receivcer
是谁,
只要这个 receivcer
有 create_file
方法就行了。
在本地新建文件或者在云端新建文件,是由接收者这一层实现的,
具体命令可以不针对新建方式再区分出本地新建命令和云端新建命令,统一的使用 NewFileCommand
。
如果创建一个 NewCloudFileCommand
也是可行的,设计模式不是教条主义,没有严格的限定不能怎样,
设计模式是经验的总结,不是放之四海而皆准的真理。
那么问题来了,谁决定 NewFileCommand
在初始化的时候 reciver
传哪一个呢?答案是客户端。
客户端与发送者
前面已经将命令模式的结构和功能描述清楚了,客户端这个概念可以理解为命令模式运行的环境, 总要有一个地方需要创建具体命令和接收者吧,它可能就是一个函数,负责编辑器的初始化。
对于客户端,难以指出一个具体的对象并将其称之为客户端,只需要牢记它的作用就好了:
客户端创建并配置具体命令对象,并让命令对象与发送者相关联。
什么又是发送者呢?发送者又称触发者,是具体命令的调用者, 前面讲了新建文件的三种途径,这3个途径不就是新建文件这个动作的触发者么?
class NewFileInvoker:
def __init__(self, command):
self.command = command
def run(self):
self.command.execute()
class NewFileButtionInvoker(NewFileInvoker):
pass
class NewFileMenuInvoker(NewFileInvoker):
pass
class NewFileKeyboard(NewFileInvoker):
pass
最后完成客户端代码。
def init_client():
new_file_receiver = NewFileReceiver()
command = NewFileCommand(new_file_receiver, 'data.txt')
buttion_invoker = NewFileButtionInvoker(command)
buttion_invoker.run()
if __name__ == '__main__':
init_client()
class AbcCommand(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def cancel(self):
pass
class ABCReceiver(ABC):
@abstractmethod
def create_file(self, filename):
pass
def del_file(self, filename):
os.remove(filename)
class NewFileReceiver(ABCReceiver):
def create_file(self, filename):
with open(filename, 'w')as f:
pass
class CloudNewFileReceiver(ABCReceiver):
def create_file(self, filename):
pass
class NewFileCommand(AbcCommand):
def __init__(self, reciver, new_file_name):
self.reciver = reciver
self.new_file_name = new_file_name
def execute(self):
self.reciver.create_file(self.new_file_name)
def cancel(self):
self.reciver.del_file(self.new_file_name)
class NewFileInvoker:
def __init__(self, command):
self.command = command
def run(self):
self.command.execute()
def cannel(self):
self.command.cancel()
class NewFileButtionInvoker(NewFileInvoker):
pass
class NewFileMenuInvoker(NewFileInvoker):
pass
class NewFileKeyboard(NewFileInvoker):
pass
现在又想撤销
def init_client():
new_file_receiver = NewFileReceiver()
command = NewFileCommand(new_file_receiver, 'data.txt')
buttion_invoker = NewFileButtionInvoker(command)
buttion_invoker.run()
buttion_invoker.cannel()
if __name__ == '__main__':
init_client()
在 AbcCommand
增加一个撤销的方法,触发者,接收者同理也增加撤销的方法和删除文件的方法,
这样就能够轻松的实现撤销方法,在 NewFileCommand
具体命令中,
包含了实现这个命令所需要的一切信息,自然也就能够轻易的实现撤销操作。
除了新建操作,还有重命名操作,删除操作,修改操作,每一种操作都实现一个具体命令对象,
每一个命令对象都有 execute
方法和 cancel
方法。
当对编辑器执行了一系列操作后,想要回退,那么只需要将之前执行过的命令保存到一个列表中,
在撤销回退时逆向调用每一个具体命令的 cancel
方法即可。