目录
1 命令模式
命令模式
将”请求”封装成对象(指命令对象
,能把方法调用封装起来),以便使用不同的请求、队列或者日志来参数化其他对象
(指调用者对象
)。命令模式也支持可撤销的操作。
- 一个
命令对象Command需要绑定一个命令接收者Receiver
,将接收者
和接收者的具体动作封装进来
,只暴露出一个execute()
方法,当此方法被调用时,就会执行接收者的动作。这样,当调用者对象
调用命令对象的execute()方法时,不需要知道是哪个接收者接收到了请求命令
,也不需要知道接收者执行了哪些动作
。 - 通过一个
抽象基类的命令接口类,可以扩展出许多具体的命令类
,这样就只需要使用接口类对象来参数化调用者对象
即可。调用者对象不需要知道具体的命令,只知道他是一个命令即可
。 - 可以
通过命令模式来实现“队列、日志和支持撤销操作”
。
在安装向导、订单系统、遥控控制系统、日程安排、工作队列等诸多现实生活场景中有应用。
2 命令模式的UML类图
Client
: 客户端类,创建一个具体的命令对象ConcreteCommand和接收者对象Receiver,并将接收者对象绑定到具体的命令对象中。Receiver
: 接收者类,接收者具有执行请求命令的具体方法action()
。Command
: 命令对象的抽象基类,所有的具体命令对象都需要实现此接口,拥有一个execute()方法,调用此方法就可以让接收者进行相关的动作,调用undo()方法可以让接收者进行撤销动作。ConcreteCommand
: 具体的命令对象类,实现了Command接口。与接收者绑定起来,调用者调用命令对象的execute()方法
就能发出请求命令
,然后由命令接收者Receiver接收命令
并执行相关的动作action()
。Invoker
: 调用者类,传入一个具体的命令对象参数到方法setCommand()中
,并在某个时间点调用命令对象的execute()方法
。
使用步骤:
- 客户端
实例化一个接收者对象receiver
和一个具体的命令对象command
,将receiver绑定到command中
。 实例化一个调用者invoker
,并在某个时间点使用一个命令对象传入到setCommand(command)方法
中,然后调用命令对象command的execute()方法
,发出请求命令
。在命令对象的execute()方法中,调用与其绑定的接收者的action()方法,执行命令
。
优势:
- 命令模式
将发出请求的调用者对象
和执行请求的接收者对象
解耦`。 - 被
解耦的两个对象通过命令对象沟通
。命令对象封装了接收者的一个或多个动作
。 - 调用者调用命令对象的execute()方法发出请求,这使得接收者的动作被调用。
- 命令对象可以
支持撤销
,做法是实现一个 undo()方法来回到execute()被执行前的状态
。 宏命令使用一系列命令对象初始化
,然后调用多个命令
。
3 观察者的一个例子:遥控家电
遥控器有7个插槽,每个插槽对应两个按钮,每个按钮对应到一个命令,这样遥控器就充当了调用者的身份。当按下按钮时,相应命令对象的execute()方法被调用,与其绑定的接收者(例如“电灯”、“天花板电扇”、“音响”)的动作就会被调用。还有一个撤销按钮,当按下时调用命令对象的undo()方法,接收者执行相关的动作。
3.1 基本功能实现
3.2 撤销按钮功能实现
3.3 宏命令功能实现
创建一个命令对象,当调用此命令对象的execute()方法后,会执行一系列命令。
4 队列请求和日志请求
4.1 队列请求
如果有一个工作队列:在一端添加命令,在另一端则是线程。线程进行下面的动作:从工作队列中选择一个命令,然后调用execute()方法,等待调用完成后,将此命令对其,再取出下一个命令。两个相邻的命令之间无需有任何关系。前一个命令可以是读取网络数据,下一个命令可以是处理财务运算。
4.2 日志请求
某些应用需要将所有动作记录在日志中。当系统死机时,重新调用这些动作恢复到之前的状态。只需要在命令中新增两个方法store()和load()即可完成。
5 命令模式的一个Python实现例子
5.1 例子解释
假如你是证券交易所的客户,会创建买入股票和卖出股票的订单(即Command)。通常情况下,你通过代理或经纪人(即Invoker)向证券交易所(即Receiver)沟通,而不是直接到证券交易所执行交易。代理负责将你的请求提交给证券交易所,完成股票买入或卖出请求。
5.2 UML类图
5.3 代码实现
- 接收者类StockTrade
- 定义了具体的动作
buy()和sell()
,由具体命令对象的execute()调用此方法
,然后执行具体动作
- 定义了具体的动作
class StockTrade:
'''
Receiver接受者,是一个知道如何做必要的工作的类
'''
def buy(self):
print("You will buy stocks.")
def sell(self):
print("You will sell stocks.")
- 命令接口类Order
抽象基类,具有一个execute()方法
from abc import ABCMeta, abstractmethod
class Order(metaclass=ABCMeta):
'''
命令Command的抽象基类,即为具体的命令类提供了一个接口
'''
@abstractmethod
def execute(self):
'''通过调用命令对象的execute()方法,可以让接收者进行相应的工作'''
pass```
- 具体的命令对象类BuyStockOrder、SellStockOrder
- 首先
构造函数__init__(stock)
使用一个接收者对象stock初始化属性stock
实现的execute()方法
中调用stock的buy()或sell()方法
- 首先
class BuyStockOrder(Order):
'''具体命令对象类'''
def __init__(self, stock):
'''通过此方法将接收者绑定'''
self.stock = stock
def execute(self):
'''命令执行方法,通过调用此方法`发出命令执行请求`,然后`调用接收者的一个或多个动作`,由`接收者执行请求`'''
self.stock.buy()
class SellStockOrder(Order):
'''具体命令对象类'''
def __init__(self, stock):
'''通过此方法将接收者绑定'''
self.stock = stock
def execute(self):
'''命令执行方法,通过调用此方法`发出命令执行请求`,然后`调用接收者的一个或多个动作`,由`接收者执行请求`'''
self.stock.sell()
- 调用者类Agent
- 首先
构造函数__init__()
初始化__orderQueue属性为一个空列表
- 传入一个具体命令对象给
placeOrder(order)方法
,并在某一时刻调用此方法发出请求
,然后执行命令对象的execute()方法
,在execute()方法
中,执行接收者的动作完成请求
。
- 首先
class Agent:
'''
Invoker调用者
'''
def __init__(self):
self.__orderQueue = []
def placeOrder(self, order):
'''调用者拥有一个命令对象列表,在某个时刻调通过此方法调用命令对象的execute()方法,将请求付诸实行'''
self.__orderQueue.append(order)
order.execute()
- 客户端
- 客户端
实例化一个接收者对象stock
和具体的命令对象buyStock、sellStock
,将stock绑定到具体命令中
。 实例化一个调用者agent
,并在某个时间点将一个命令对象传入到placeOrder(order)方法
中,然后调用命令对象的execute()方法
,发出请求命令
。在命令对象的execute()方法中,调用与其绑定的接收者的buy()方法或sell()方法,执行命令
。
- 客户端
if __name__ == '__main__':
# Client
stock = StockTrade() # 客户先实例化一个接收者对象
buyStock = BuyStockOrder(stock) # 然后将接收者对象与具体的命令对象绑定起来
sellStock = SellStockOrder(stock) # 将接收者对象与具体的命令对象绑定起来
# Invoker
agent = Agent() # 实例化一个调用者对象
agent.placeOrder(buyStock) # 将命令对象与调用者绑定起来,调用者`发出命令执行请求`
agent.placeOrder(sellStock) # 将命令对象与调用者绑定起来,`发出命令执行请求`