命令模式小试

命令模式是一种原理非常简单,但是使用起来非常优雅、方便的并且个人觉得很有艺术感的设计模式。

一、介绍

还是先来看一下《研磨设计模式》的定义——将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。

命令模式的本质——封装请求。

封装请求是什么意思呢?其实在Java中随处可见。最简单的就像在Swing中,每次触发一个事件,都会产生一个相应的事件对象,这个事件对象封装了这个事件所有的参数,我们通过这个事件对象来对这个事件进行处理。同样的,我们每次触发了一个异常,这个异常的所有参数、包括堆栈信息都会被封装在一个异常对象中,它代表这个异常事件本身,然后由我们来处理或抛出。

这样讲来,命令模式不过是将一个请求封装起来罢了。然而,这里面却大有文章可作。

二、我的实现

我们在写文档的时候,作为命令对象接受者的文本文档,它可能接收的是我们在键盘的一个简单的字符输出的命令,有可能接收的是改变光标位置的命令,也可能接收的是我们的删除命令;在进行这些操作的时候,也有可能接收的是我们的撤销操作命令。假设我们的文本文档只能接收这些命令。

现在我们用命令模式封装这些命令模拟一下在文档上进行文本操作的过程。1、首先我们有一个命令接口,如下:


1 public interface Command {
2
3 //处理
4 public void execute();
5 //恢复
6 public void recover();
7 }

2、按照计算机的原理,我们对文本操作的命令应该是键盘或鼠标将命令发送给外设驱动,外设驱动发送给CPU,然后CPU发送给显卡,显卡发送给屏幕,进行显示。这里我们省略繁琐的命令传递,将命令直接交给屏幕。由于对屏幕的操作都是属于屏幕本身的操作,这里不用接口了,构建屏幕类如下:


 1 public class Screen {
2
3 //设置为单例
4 private static Screen screen = new Screen();
5 //当前屏幕内容
6 private StringBuilder screenContent = new StringBuilder();
7 //此次刷新前屏幕内容
8 private StringBuilder oldContent = screenContent;
9 //屏幕光标位置,默认为屏幕内容最尾
10 private int pos = screenContent.length();
11
12 private Screen(){
13 }
14
15 public static Screen getInstance(){
16 return screen;
17 }
18 //指定位置插入字符
19 public void insert(int pos, String content){
20 oldContent = screenContent;
21 screenContent.insert(pos, content);
22 this.pos = pos + content.length();
23 }
24 //插入字符
25 public void insert(String content){
26 this.insert(pos,content);
27 }
28
29 //返回当前光标位置
30 public int getPos()
31 {
32 return pos;
33 }
34
35 //设置当前光标位置
36 public void setPos(int pos)
37 {
38 this.pos = pos;
39 }
40
41 //返回屏幕内容
42 public String getScreenContent()
43 {
44 return screenContent.toString();
45 }
46 //可用来覆盖屏幕内容
47 public void setScreenContent(String src)
48 {
49 oldContent = screenContent;
50 this.screenContent = new StringBuilder(src);
51 pos = src.length();
52 }
53
54 //返回此次刷新前屏幕内容
55 public String getOldContent()
56 {
57 return oldContent.toString();
58 }
59
60 //清空屏幕
61 public void clear(){
62 oldContent = screenContent;
63 screenContent = new StringBuilder();
64 pos = 0;
65 }
66
67 //输出
68 public void outPut(){
69 System.out.println(new SimpleDateFormat("hh:mm:ss").format(new Date())+",当前光标位置:"+pos+",屏幕刷新输出:"+screenContent);
70 }
71
72
73 }

3、好了,我们来构建第一个书写命令,如下:


 1 public class WriteCommand implements Command {
2 // 写之前屏幕的内容
3 private String oldScreenContent;
4 //移动光标前光标位置
5 private int oldPos;
6 //要写的
7 private String content;
8 private Screen screen = Screen.getInstance();
9
10 public void setScreen(Screen screen)
11 {
12 this.screen = screen;
13 }
14
15 //构造方法传入要写的内容
16 public WriteCommand(String str)
17 {
18 this.content = str;
19 }
20
21 // 执行
22 @Override
23 public void execute()
24 {
25 System.out.println("-----------------------写命令!内容为:"+content);
26 oldPos = screen.getPos();
27 oldScreenContent = screen.getScreenContent();
28 // 屏幕追加新内容
29 screen.insert(content);
30 // 屏幕刷新输出
31 screen.outPut();
32 }
33
34 // 恢复
35 @Override
36 public void recover()
37 {
38 System.out.println("-----------------------写命令!撤销内容为:"+content);
39 screen.setScreenContent(oldScreenContent);
40 screen.setPos(oldPos);
41 // 屏幕刷新输出
42 screen.outPut();
43 }
44
45 }

可以看到,这个命令持有的属性包括输入的内容、输入前光标位置、输入前屏幕的内容和输入的屏幕。

4、同样的,我们可以构建一个移动光标的命令,如下:


 1 //移动光标命令
2 public class MoveCursorCommand implements Command {
3
4 //移动光标前光标位置
5 private int oldPos;
6 //移动光标后光标位置
7 private int currentPos;
8
9 private Screen screen = Screen.getInstance();
10
11 public void setScreen(Screen screen)
12 {
13 this.screen = screen;
14 }
15
16 public MoveCursorCommand(int currentPos)
17 {
18 this.currentPos = currentPos;
19 }
20
21 @Override
22 public void execute()
23 {
24 System.out.println("-----------------------移动光标命令,光标移动至" + currentPos);
25 oldPos = screen.getPos();
26 screen.setPos(currentPos);
27 screen.outPut();
28 }
29
30 @Override
31 public void recover()
32 {
33 System.out.println("-----------------------移动光标命令,恢复光标至" + oldPos);
34 screen.setPos(oldPos);
35 screen.outPut();
36 }
37
38 }

可以看到,这个命令持有的属性包括移动前光标的位置、移动后光标的位置,还有输出的屏幕。

5、接下来构建一个删除的命令,如下:


 1 public class DeleteCommand implements Command {
2
3 //要删除的长度
4 private int deleteLength;
5 //删除前屏幕内容
6 private String oldScreenContent;
7 //删除前光标的位置
8 private int oldPos;
9 private Screen screen = Screen.getInstance();
10
11 public void setScreen(Screen screen)
12 {
13 this.screen = screen;
14 }
15
16 //构造方法,传入要删除的长度
17 public DeleteCommand(int deleteLength)
18 {
19 this.deleteLength = deleteLength;
20 }
21
22 // 默认删除一格
23 public DeleteCommand()
24 {
25 this(1);
26 }
27
28 @Override
29 public void execute()
30 {
31 System.out.println("-----------------------删除命令!删除"+deleteLength+"个字符!");
32 oldPos = screen.getPos();
33 oldScreenContent = screen.getScreenContent();
34 // 如果删除长度大于当前光标位置,删除光标前所有内容
35 if (deleteLength >= screen.getPos())
36 {
37 screen.setScreenContent(screen.getScreenContent().substring(screen.getPos()));
38 // 否则
39 }
40 else
41 {
42 String newContent = screen.getScreenContent().substring(screen.getPos()) + screen.getScreenContent().substring(0, screen.getPos() - deleteLength);
43 screen.setScreenContent(newContent);
44 }
45 screen.outPut();
46 }
47
48 @Override
49 public void recover()
50 {
51 System.out.println("-----------------------删除命令!恢复所删除的"+deleteLength+"个字符!");
52 screen.setScreenContent(oldScreenContent);
53 screen.setPos(oldPos);
54 screen.outPut();
55 }
56
57 }

可以看到,这个命令对象持有属性包括要删除的长度、删除前屏幕的内容、删除前光标的位置和输出的屏幕。

我们可以看到,每个命令的属性都不尽相同,都包含了其主要的特征。而真正操作这些命令的,是屏幕类。6、下面进行简单的测试:


 1 public class Test {
2
3 public static void main(String[] args)
4 {
5 //写命令
6 Command write = new WriteCommand("abcd");
7 //移动光标命令
8 Command moveCursor = new MoveCursorCommand(3);
9 //写命令
10 Command write2 = new WriteCommand("123456789");
11 //删除命令
12 Command delete = new DeleteCommand(5);
13
14 //执行这些命令
15 write.execute();
16 moveCursor.execute();
17 write2.execute();
18 delete.execute();
19 //恢复一下
20 write2.recover();
21 }
22 }

6、结果如下:


-----------------------写命令!内容为:abcd
05:11:06,当前光标位置:4,屏幕刷新输出:abcd
-----------------------移动光标命令,光标移动至3
05:11:06,当前光标位置:3,屏幕刷新输出:abcd
-----------------------写命令!内容为:123456789
05:11:06,当前光标位置:12,屏幕刷新输出:abc123456789d
-----------------------删除命令!删除5个字符!
05:11:06,当前光标位置:8,屏幕刷新输出:dabc1234
-----------------------写命令!撤销内容为:123456789
05:11:06,当前光标位置:3,屏幕刷新输出:abcd

可见,每一步命令操作都保存了操作前的屏幕状态,可恢复到任意操作处去。

这种方式有一个缺点,那就是每次操作前都保存了屏幕的所有状态,会消耗大量内存,所以,我们可以用一个队列列表来放这些命令,列表容量是可设置的。超过容量的操作不再保存,即不可恢复。同时这也有一个优点,那就是可以很方便的一键撤销上一步操作。而这个就是命令模式的队列请求。下面我们来试一下。

三、队列请求

1、命令队列如下:


 1 public class CommandQueue {
2
3 private static LinkedList<Command> commands = new LinkedList<Command>();
4
5 public synchronized static void addAndExecute(Command cmd){
6 //大于10,就移除第一个
7 if(commands.size() > 10) {
8 commands.remove();
9 }
10 commands.add(cmd);
11 cmd.execute();
12 }
13
14 public synchronized static void recover(Command cmd){
15 if(cmd == null){
16 System.out.println("命令不存在!");
17 }else {
18 cmd.recover();
19 }
20 }
21 }

2、测试代码也要改一下:


 1 public class Test {
2
3 public static void main(String[] args)
4 {
5 //写命令
6 Command write = new WriteCommand("abcd");
7 //移动光标命令
8 Command moveCursor = new MoveCursorCommand(3);
9 //写命令
10 Command write2 = new WriteCommand("123456789");
11 //删除命令
12 Command delete = new DeleteCommand(5);
13
14 //将这些命令依次放入命令列表并执行
15 CommandQueue.addAndExecute(write);
16 CommandQueue.addAndExecute(moveCursor);
17 CommandQueue.addAndExecute(write2);
18 CommandQueue.addAndExecute(delete);
19
20 //恢复一下
21 CommandQueue.recover(write2);
22 }
23 }

结果与之前的一样。

可以看到,将请求封装之后,我们能够做的事情就太多了。我们还可以将请求打包,批量执行。还可以将请求对象持久化以便随时恢复。这里就不必演示了。

命令模式小试

时间: 2024-11-05 14:58:04

命令模式小试的相关文章

5.5 进入编辑模式 5.6 vim命令模式 5.7 vim实践

5.5 进入编辑模式 5.6 vim命令模式 5.7 vim实践 扩展 vim的特殊用法 http://www.apelearn.com/bbs/thread-9334-1-1.html vim常用快捷键总结 http://www.apelearn.com/bbs/thread-407-1-1.html vim快速删除一段字符 http://www.apelearn.com/bbs/thread-842-1-1.html vim乱码 http://www.apelearn.com/bbs/thr

Ubuntu命令模式基础

Ubuntu是一个自由.开源.基于Debian的Linux发行版.在ubuntu的基础上,又衍生其它各具特色的Linux发行版.首先是一个操作系统,操作系统用于管理电脑硬件.要发挥出电脑的作用,还得依靠安装各种应用软件. 其相关的简单命令如下: (1)查看当前文件夹下的目录 ①.ls (list的缩写)命令用于列出文件和目录.默认上,他会列出当前目录的内容.带上参数后,可以以不同的方式显示.如图: ls命令可以列出当前目录的内容.dir命令是ls命令的一个别名,也是directory的缩写.通常

【游戏设计模式】之二 实现撤消重做、回放系统的神器:命令模式

本系列文章由@浅墨_毛星云 出品,转载请注明出处.   文章链接:http://blog.csdn.net/poem_qianmo/article/details/52663057 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 这篇文章将与大家一起探索游戏开发中命令模式的用法. 命令模式的成名应用是实现诸如撤消,重做,回放,时间倒流之类的功能.如果你想知道<Dota2>中的观战系统.<魔兽争霸3>中的录像系统.<守望先锋>

命令模式

1.命令模式:将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化:对请求排列或记录请求日志,以及支持可以撤销的操作. 2.优点:(1).他能比较容易的设计一个命令队列:(2).在需要的情况下,比较容易的将命令记入日志:(3).允许接收请求的一方决定是否否决请求:(4).可以容易地实现对请求的撤销和重做:(5).由于加进新的具体命令类不影响其它类,因此增加新的具体命令很容易.(6).把请求一个操作的对象与指导怎么执行操作对象分隔开. 3.注意:不要为代码添加基于猜测的.实际不需要的

【设计模式】 模式PK:命令模式VS策略模式

1.概述 命令模式和策略模式的类图确实很相似,只是命令模式多了一个接收者(Receiver)角色.它们虽然同为行为类模式,但是两者的区别还是很明显的.策略模式的意图是封装算法,它认为"算法"已经是一个完整的.不可拆分的原子业务(注意这里是原子业务,而不是原子对象),即其意图是让这些算法独立,并且可以相互替换,让行为的变化独立于拥有行为的客户:而命令模式则是对动作的解耦,把一个动作的执行分为执行对象(接收者角色).执行行为(命令角色),让两者相互独立而不相互影响. 我们从一个相同的业务需

设计模式—命令模式

1 概述 将一个请求封装为一个对象(即我们创建的Command对象),从而使你可用不同的请求对客户进行参数化:对请求排队或记录请求日志,以及支持可撤销的操作. 2 解决的问题 在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录.撤销或重做.事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适. 3 模式中的角色 3.1 抽象命令(Command):定义命令的接口,声明执行的方法. 3.2 具体命令(ConcreteCommand):具体命令,实现要执

javascript设计模式详解之命令模式

每种设计模式的出现都是为了弥补语言在某方面的不足,解决特定环境下的问题.思想是相通的.只不过不同的设计语言有其特定的实现.对javascript这种动态语言来说,弱类型的特性,与生俱来的多态性,导致某些设计模式不自觉的我们都在使用.只不过没有对应起来罢了.本文就力求以精简的语言去介绍下设计模式这个高大上的概念.相信会在看完某个设计模式之后有原来如此的感慨. 一.基本概念与使用场景: 基本概念: 将请求封装成对象,分离命令接受者和发起者之间的耦合. 命令执行之前在执行对象中传入接受者.主要目的相互

设计模式完结(14)-- 命令模式 --- 请求发送者与接收者解耦

起连接作用:  类似开关   和  电器  之间的   电线 请求发送者与接收者解耦,  引入命令类 abstract class Command { public abstract void execute(); } class Invoker { private Command command; //构造注入 public Invoker(Command command) { this.command = command; } //设值注入 public void setCommand(Co

命令模式(Command Pattern)

命令模式:将“请求”封装成对象,以便使用不同的请求.队列或者日志来参数化其他对象.命令模式也支持可撤销的操作. 模式类图: 下面举一个具体的例子: 有一个遥控器类RemoteControl,它可以控制Light类的行为,一个Command命令接口,两个实现Command接口的具体命令,分别实现开灯.关灯命令. 客户Client在使用这个遥控器的时候,先实例化一个RemoteControl类,如果它要开灯,那么就实例化LightOnCommand具体命令,将要控制的Light对象传入,调用setC