详解command设计模式,解耦操作和回滚

大家好 , 欢迎来到设计模式专题 , 我们的主旨是介绍一些有趣好玩的设计模式 。
今天我们介绍的设计模式叫做命令模式(command) , 在这个模式下 , 我们可以实现do和undo的解耦 , 让使用方不用关心内部的实现细节 。
command模式这个模式我们在日常当中经常使用 , 举一个很简单的例子 , 比如说我们发布代码 。 发布了之后发现不小心发布上去了一个bug , 这个时候我们应该做什么?很简单 , 就是回滚 , 把线上的代码回滚到这一次发布之前的代码 。 这样我们这次发布带来的改动就会被消除 , 那么就避免了bug的产生 。
那么 , 对于一个发布系统来说 , 它需要做什么?其实也就是两个功能 , 一个是发布另外一个是回滚 。 这两个操作是互相可逆的 , 对于它的使用者来说 , 是不会关心它的内部是如何实现的 , 我们只需要在页面上按按钮就好了 。
我们来回顾一下这个过程 , 我们点击发布 , 可以把最新的代码发布上线 。 发布之后发现问题 , 再点击回滚 , 系统再自动恢复到发布之前的状态 。 发布和回滚彼此是可逆的 , 当我们消除掉bug之后 , 再次点击发布 , 又可以再次发布最新的代码了 。
command模式就是做的这个事情 , 也就是对do和undo的封装 。 我们来看一个很简单的例子 , 对文件改名 。 比如说我们要把系统当中的文件改名 , 从A.txt改成B.txt 。 这个功能很简单 , 系统为我们提供了现成的函数 , 叫做os.rename() , 我们只需要把A和B两个文件的地址传入其中即可 。
假如我们发现改名字改错了 , 想回滚怎么办呢?会发现我们改动之前的名字已经忘了 , 不知道怎么回滚了 。 这个时候就可以使用command模式 , 我们来看代码:
import osclass MoveFileCommand:def __init__(self, src, dest):self.src = http://kandian.youth.cn/index/srcself.dest = destdef execute(self):self.rename(self.src, self.dest)def undo(self):self.rename(self.dest, self.src)def rename(self, src, dest):print('renaming from {} to {}'.format(src, dest))os.rename(src, dest)在execute方法当中 , 我们把文件从src变成了dest , 如果想要回滚 , 它又会再次调用rename 。 将文件名从dest回滚到src 。 这样的话 , 作为使用方就可以完全不用理解api内部的实现逻辑了 , 不然的话为了防止改错了的情况 , 还需要做很多适配 。
menu item有了command模式之后我们可以在外面在封装一层用来ui交互上 , 我们很常见的一种UI交互方式就是按钮 。 某一个按钮点一下之后会出现一个按过的标记 , 并且实现一个什么功能 。 再按一次标记消失 , 功能也随之关闭 。
我随便找了一个例子 , 比如下图菜单当中的show minimap , show breadcrumbs这些都是这样的功能 。 点一下出现缩略图 , 再点一下缩略图消失 。
详解command设计模式,解耦操作和回滚文章插图
如果你写过UI页面的话 , 一般来说我们会先定义一个Menu Item的类 , 表示菜单当中的所有的item的基类 。 不同的选项表示不同的item , 我们进一步分析会发现有些item我们需要这样双击关闭的机制 , 而有些item是没有的 。 比如上面的Run、Output这些item都是点一次执行一次的 。
我们当然可以把上面介绍的Command对象直接当做item , 但是这样不利于整个菜单的统一 , 所以我们还会在外面包一层 。 比如所有MenuItem的父类应该是这样的:
class MenuItemBaseClass:def __init__(self):passdef pressed(self):passdef unpress(self):pass有了这个基类之后 , 我们就可以实现一个可回滚的类 , 将command的对象作为类成员变量 , 再在其中实现unpress方法:
【详解command设计模式,解耦操作和回滚】