命令设计模式的引出
行为模式侧重于对象的响应性。它利用对象之间的交互实现更强大的功能。命令模式也是一种行为设计模式,其中对象用于封装在完成一项操作时或在触发一个事件时所需的全部信息。
包含以下信息:
- 方法名称
- 拥有方法的对象
- 方法参数的值
了解命令设计模式
命令模式通常使用以下术语:Command、Receiver、Invoker和Client:
- Command对象了解Receiver对象的情况,并能调用Receiver对象的方法;
- 调用者方法的参数值存储在Command对象中;
- 调用者知道如何执行命令;
- 客户端用来创建Command对象并设置其接收者。
命令模式能完成的:
- 将请求封装为对象;
- 可用不同的请求对客户进行参数化;
- 允许将请求保存在队列中;
- 提供面向对象的回调;
适用于场景
- 根据需要执行的操作对对象进行参数化;
- 将操作添加到队列并在不同地点执行请求;
- 创建一个结构来根据较小操作完成高级操作;
代码实现命令设计模式
代码实现了命令设计模式。假设我们想要开发一个安装向导,或者更常见的安装程序。安装意味着需要根据用户做出的选择来复制或移动文件系统中的文件。在下面的示例中,我们首先在客户端代码中创建Wizard对象,然后使用preferences()方法存储用户在向导的各个屏幕期间做出的选择。在向导中单击Finish按钮时,就会调用execute()方法。之后,execute()方法将会根据首选项来开始安装:
class Wizard():
def __init__(self, src, rootdir):
self.choices = []
self.rootdir = rootdir
self.src = src
def preferences(self, command):
self.choices.append(command)
def execute(self):
for choice in self.choices:
if list(choice.values())[0]:
print("Copying binaries--", self.src, "to", self.rootdir)
else:
print("No Operation")
if __name__ == '__main__':
# Client code
wizard = Wizard("python3.5.gzip", '/usr/bin/')
# Users chooses to install python only
wizard.preferences({'python': True})
wizard.preferences({'java': False})
wizard.execute()
输出:
命令模式的UML类图
命令模式的主要参与者为:Command、ConcreteCommand、Receiver、Invoker和Client。
在上图中:
- **Command:**声明执行操作的接口;
- **ConcreteCommand:**将一个Receiver对象和一个操作绑定在一起;
- **Client:**创建ConcreteCommand对象并设定其接收者;
- **Invoker:**要求该ConcreteCommand执行这个请求;
- **Receiver:**知道如何实施与执行一个请求相关的操作;
整个流程图是非常简单的,客户端请求执行命令,调用者接受命令,封装它并将其放置到队列中。ConcreteCommand类根据所请求的命令来指导接收者执行特定的动作。
现在来个实例:
from abc import ABCMeta
class Command(metaclass=ABCMeta):
def __init__(self, recv):
self.recv = recv
def execute(self):
pass
class ConcreteCommand(Command):
def __init__(self, recv):
self.recv = recv
def execute(self):
self.recv.action()
class Receiver:
def action(self):
print("Receiver Action")
class Invoker:
def command(self, cmd):
self.cmd = cmd
def execute(self):
self.cmd.execute()
if __name__ == '__main__':
recv = Receiver()
cmd = ConcreteCommand(recv)
invoker = Invoker()
invoker.command(cmd)
invoker.execute()
输出:Receiver Action
命令模式实例
通过一个(在互联网世界中经常讲到的)证券交易所的例子来演示命令模式的实现。在证券交易所会发生哪些事情呢?作为证券交易所的用户,你会创建买入或卖出股票的订单。通常情况下,你无法直接执行买入或卖出。实际上,代理或经纪人,在你和证券交易所之间扮演了中介的角色。代理负责将你的请求提交给证券交易所,完成工作。我们假设你想在星期一早上开市后卖出股票。但是在星期日晚上,虽然交易所尚未开市,你就可以向代理提出卖出股票的请求。然后,代理会将该请求放入排队,以便在星期一早晨当交易所开市的时候执行该请求,完成相应的交易。
示例:
from abc import ABCMeta, abstractmethod
# Command对象由Order类表示;
# Order提供了一个接口(Python的抽象基类),以便ConcreteCommand可以实现该行为;
# execute()方法是需要由执行Order类的ConcreteCommand类来定义的抽象方法。
# 抽象类Order和抽象方法execute():
class Order(metaclass=ABCMeta):
@abstractmethod
def execute(self):
pass
# 表示ConcreteCommand的某些类:
# BuyStockOrder和SellStockOrder,它们实现了Order接口;
# 这两个ConcreteCommand类都使用股票交易系统的对象,所以它们可以为交易系统定义适当的操作;
# 每个ConcreteCommand类的execute()方法使用股票交易对象执行买入和卖出操作。
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()
# StockTrade类表示该示例中的Receiver对象;
# 它定义了多个方法(动作)来执行ConcreteCommand对象发出的订单;
# buy()和sell()方法由接收者定义,分别由BuyStockOrder和SellStockOrder调用以在交易所中买入或卖出股票。
class StockTrade:
def buy(self):
print("You will buy stocks")
def sell(self):
print("You will sell stocks")
"""
Agent类表示调用者;
代理是客户端和StockExchange之间的中介,并执行客户下达的订单;
代理定义了一个作为队列的数据成员__orderQueue(列表),客户端下达的任何新订单都将添加到队列中;
代理的placeOrder()方法负责对订单排序以及执行订单。
"""
class Agent:
def __init__(self):
self.__orderQueue = []
def placeOrder(self, order):
self.__orderQueue.append(order)
order.execute()
if __name__ == '__main__':
# Client
stock = StockTrade()
buyStock = BuyStockOrder(stock)
sellStock = SellStockOrder(stock)
# Invoker
angent = Agent()
angent.placeOrder(buyStock)
angent.placeOrder(sellStock)
命令模式在云应用中的一些实现
重做或回滚操作:
在实现回滚或重做操作时,开发人员可以做两件不同的事情;
这些是在文件系统或内存中创建快照,当被要求回滚时,恢复到该快照;
使用命令模式时,可以存储命令序列,并且要求进行重做时,重新运行相同的一组操作即可。
异步任务执行:
在分布式系统中,我们通常要求设备具备异步执行任务的功能,以便核心服务在大量请求涌来时不会发生阻塞。
在命令模式中,Invoker对象可以维护一个请求队列,并将这些任务发送到Receiver对象,以便它们可以独立于主应用程序线程来完成相应的操作。
命令模式的优缺点
优点:
- 将调用操作的类与知道如何执行该操作的对象解耦。
- 提供队列系统后,可以创建一系列命令。
- 添加新命令更加容易,并且无需要更改现有代码。
- 还可以使用命令模式来定义回滚系统,例如,在向导示例中,我们可以编写一个回滚方法。
缺点:
5. 为了实现目标,需要大量的类和对象进行协作。
6. 每个单独的命令都是一个ConcreteCommand类,从而增加了需要实现和维护的类的数量。
常见问答:
1.命令模式中是否可以不实现Receiver和ConcreteCommand?
答:是的,可以许多软件应用程序也就是通过这种方式来使用命令模式的。这里唯一要注意的是调用者和接受者之间的交互。如果接收器未被定义的话,则去耦程度就会下降;此外,参数啊命令的优势也就不复存在了。
- 我使用什么数据结构来实现Invoker对象中的队列机制?
答:在上面示例中,使用了一个列表来实现队列。但是命令模式还可以使用一个堆栈来实现队列机制,这在开发具有重做或回滚功能的时候非常有帮助。