#命令模式-----封装调用
##一、命令设计模式简介
行为模式侧重于对象的响应性,它利用对象之间的交互实现更强大的功能。命令模式也是一种行为设计模式,其中对象用于封装在完成一项操作时或在触发一个事件时所需的全部信息。这些信息包括以下内容:
(1)方法名称;
(2)拥有方法的对象;
(3)方法参数的值。
##二、了解命令设计模式
命令模式通常使用以下术语:Command、Receiver、Invoker和Client:
(1)Command对象了解Receiver对象的情况,并能调用Receiver对象的方法;
(2)调用者方法的参数值存储在Command对象中;
(3)调用者知道如何执行命令;
(4)客户端用来创建Command对象并设置其接收者。
命令模式的主要意图如下:
(1)将请求封装为对象;
(2)可用不同的请求对客户端进行参数化;
(3)允许将请求保存在队列中;
(4)提供面向对象的回调。
命令模式可用于以下各种场景:
(1)根据需要执行的操作对对象进行参数化;
(2)将操作添加到队列并在不同地点执行请求;
(3)创建一个结构来根据较小操作完成高级操作。
在下面的实例中,我们首先在客户端代码中创建Wizard对象,然后使用preferences()方法存储用户在向导的各个屏幕期间做出的选择。在向导中单击Finish按钮时,就会调用execute()方法。之后,execute()方法将会根据首选项来开始安装:
class Wizard:
def __init__(self, src, rootdir):
self.choices = []
self.src = src
self.rootdir = rootdir
def preferences(self, command):
self.choices.append(command)
def execute(self):
for choice in self.choices:
if list(choice.values())[0]:
print(list(choice.values())[0])
print('Copying binaries --', self.src, ' to ', self.rootdir)
else:
print('No Operation!')
if __name__ == '__main__':
wizard = Wizard('python3.7.gzip', '/user/bin/')
wizard.preferences({'python': True})
wizard.preferences({'java': False})
print('choices:', wizard.choices)
wizard.execute()
##三、命令模式的UML类图
通过该UML图不难发现,该模式主要涉及5个参与者:
- Command:声明执行操作的接口;
- ConcreteCommand:将一个Receiver对象和一个操作绑定在一起;
- Client:创建ConcreteCommand对象并设定其接收者;
- Invoker:要求该ConcreteCommand执行这个请求;
- Receiver:知道如何实施与执行一个请求相关的操作。
整个流程图是非常简单的,客户端请求执行命令,调用者接收命令,封装它并将其放置到队列中。ConcreteCommand类根据所请求的命令来指导接收者执行特定的动作。参考以下代码,进一步了解该模式中所有的参与者的情况:
from abc import ABCMeta, abstractmethod
class Command(metaclass = ABCMeta):
def __init__(self, recv):
self.receiver = recv
def execute(self):
pass
class ConcreteCommand(Command):
def __init__(self, recv):
self.receiver = recv
def execute(self):
self.receiver.action()
class Receiver:
def action(self):
print('Receiver action')
class Invoker:
def command(self, cmd):
self.command = cmd
def execute(self):
self.command.execute()
if __name__ == '__main__':
recv = Receiver()
cmd = ConcreteCommand(recv)
invoker = Invoker()
invoker.command(cmd)
invoker.execute()
##四、实现现实世界中命令模式
作为证券交易所的用户,你会创建买入或卖出股票的订单。通常情况下,你无法直接执行买入或卖出。实际上,代理或经纪人在你和证券交易所之间扮演了中介的角色。代理负责将你的请求提交给证券交易所,完成工作。假设你想在星期一早上开市后卖出股票,但是在星期日晚上,虽然股票交易所尚未开市,你就可以向代理提出卖出股票的请求。然后,代理会将该请求放入排队,以便在星期一早晨当交易所开市的时候执行该请求,完成相应的交易。
我们应该创建一个Order接口,来定义客户端下达的订单。还应该定义ConcreteCommand类来买卖股票。此外,还需要为证券交易所定义一个类。我们应该定义实际执行交易的Receiver类,以及接收订单并交由接收者执行的代理(称为调用者)。
代码实现:
from abc import ABCMeta, abstractmethod
#Command对象:
class Order(metaclass = ABCMeta):
@abstractmethod
def execute(self):
pass
#ConcreteCommand对象:
#这两个类都使用股票交易系统的对象,所以它们可以为交易系统定义适当的操作
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()
#Receiver对象:股票交易系统
class StockTrade:
def buy(self):
print('You will buy stocks')
def sell(self):
print('You will sell stocks')
#调用者:客户端和StockTrade之间的中介,并执行客户下达的订单
class Agent:
def __init__(self):
self.__orderQueue = [] #存储客户下达的订单
def placeOrder(self, order):
self.__orderQueue.append(order)
order.execute()
if __name__ == '__main__':
stock = StockTrade()
buyStock = BuyStockOrder(stock)
sellStock = SellStockOrder(stock)
agent = Agent()
agent.placeOrder(buyStock)
agent.placeOrder(sellStock)
##五、命令模式的优缺点
命令模式具有以下优点:
- 将调用操作的类与知道如何执行该操作的对象解耦;
- 提供队列系统后,可以创建一系列命令;
- 添加新命令更加容易,并且无需更改现有代码;
- 还可以使用命令模式来调用回滚系统,例如,在向导实例中,我们可以编写一个回滚方法。
下面是命令模式的缺点:
- 为了实现目标,需要大量的类和对象进行协作。应用程序开发人员为了正确开发这些类,需要加倍小心;
- 每个单独的命令都是一个ConcreteCommand类,从而增加了需要实现和维护的类的数量。