设计模式:命令(Command)模式
一、前言
命令也是类,将命令作为一个类来保存,当要使用的时候可以直接拿来使用,比如脚本语言写出的脚本,只需要一个命令就能执行得到我们想要的需要操作很长时间才能得到的结果。这是一个非常有意思的模式,将操作的步骤保存下来,本例之中我们使用java自带的GUI来画图,然后将画图的过程(在哪个地方画了什么东西)保存下来,可以把每一次我们的操作作为一个命令,其实就是,将这个命令对应的对象保存到所有命令对象的集合之中去,这样命令集合就记录下来了每一个命令,如果要显示画的内容的时候,直接将这些命令组合读取出来在进行一次重画即可。通过这种模式保存下来已经执行的步骤,通过重画再复述出来,是一种非常重要的开发理念,在需要保存历史纪录并恢复的场合是非常有用的。
二、代码
Command接口:
1 packagezyr.dp.command;2
3 public interfaceCommand {4 public abstract voidexecute();5 }
DrawCommand类:
1 packagezyr.dp.command;2
3 importjava.awt.Point;4
5
6 public class DrawCommand implementsCommand {7
8 privateDrawable drawable;9 privatePoint position;10 publicDrawCommand(Drawable drawable,Point position){11 this.drawable=drawable;12 this.position=position;13 }14
15 public voidexecute() {16 drawable.draw(position.x, position.y);17 }18
19 }
MacroCommand 类:
1 packagezyr.dp.command;2
3 importjava.util.Iterator;4 importjava.util.Stack;5
6 public class MacroCommand implementsCommand {7
8 Stack commands=newStack();9
10 public voidexecute() {11 Iterator it =commands.iterator();12 while(it.hasNext()){13 Command command=(Command)it.next();14 command.execute();15 }16 }17
18 public voidappend(Command command){19 if(command!=this){20 commands.add(command);21 }22 }23
24 public voidclear(){25 commands.clear();26 }27
28 public voidundo(){29 if(!commands.isEmpty()){30 commands.pop();31 }32 }33
34 }
Drawable接口:
1 packagezyr.dp.command;2
3 public interfaceDrawable {4
5 public abstract void draw(int x,inty);6
7 }
DrawCanvas 实现类:
1 packagezyr.dp.command;2
3 import java.awt.*;4 importjava.util.Random;5
6
7 public class DrawCanvas extends Canvas implementsDrawable {8
9 private static final long serialVersionUID = 1972130370393242746L;10
11 privateMacroCommand history;12 private int radius=8;13
14 public DrawCanvas(int width,inthieght, MacroCommand history){15 setSize(width,hieght);16 setBackground(Color.white);17 this.history=history;18 }19
20 public void draw(int x, inty) {21 Random random = newRandom();22
23 Graphics g =getGraphics();24 g.setColor((random.nextBoolean())?Color.yellow : Color.MAGENTA);25 g.fillOval(x-radius, y-radius, radius*2, radius*2);26 }27
28 @Override29 public voidpaint(Graphics g) {30 System.out.println("执行一次刷新!"+System.currentTimeMillis());31 history.execute();32 }33
34 }
Main类:
1 packagezyr.dp.command;2
3 importjava.awt.event.ActionEvent;4 importjava.awt.event.ActionListener;5 importjava.awt.event.MouseEvent;6 importjava.awt.event.MouseMotionListener;7 importjava.awt.event.WindowEvent;8 importjava.awt.event.WindowListener;9
10 import javax.swing.*;11
12
13 public class Main extends JFrame implementsActionListener,MouseMotionListener,WindowListener{14
15 private MacroCommand history=newMacroCommand() ;16
17 private JButton btnClear=new JButton("清除");18 private JButton btnRePaint=new JButton("重现");19
20 private DrawCanvas canvas=new DrawCanvas(400,400,history);21
22 publicMain(String title){23 super(title);24
25 this.addWindowListener(this);26 canvas.addMouseMotionListener(this);27 btnClear.addActionListener(this);28 btnRePaint.addActionListener(this);29
30 Box btnBox=newBox(BoxLayout.X_AXIS);31 btnBox.add(btnClear);32 btnBox.add(btnRePaint);33
34 Box mainBox=newBox(BoxLayout.Y_AXIS);35 mainBox.add(btnBox);36 mainBox.add(canvas);37
38 getContentPane().add(mainBox);39
40 pack();41 show();42 }43
44 public static voidmain(String[] args) {45
46 new Main("命令模式");47
48 }49
50
51 @Override52 public voidactionPerformed(ActionEvent e) {53 if(e.getSource()==btnClear){54 history.clear();55 canvas.repaint();56 }else if(e.getSource()==btnRePaint){57 canvas.repaint();58 }59 }60
61
62 @Override63 public voidmouseDragged(MouseEvent e) {64 Command cmd=newDrawCommand(canvas,e.getPoint());65 history.append(cmd);66 cmd.execute();67 }68
69 @Override70 public voidwindowClosing(WindowEvent e) {71 System.exit(0);72 }73
74
75
76
77 @Override78 public voidwindowOpened(WindowEvent e) {79 }80
81 @Override82 public voidwindowClosed(WindowEvent e) {83 }84
85 @Override86 public voidwindowIconified(WindowEvent e) {87 }88
89 @Override90 public voidwindowDeiconified(WindowEvent e) {91 }92
93 @Override94 public voidwindowActivated(WindowEvent e) {95 }96
97 @Override98 public voidwindowDeactivated(WindowEvent e) {99 }100
101 @Override102 public voidmouseMoved(MouseEvent e) {103 }104 }
实验结果:
由此我们可以看到保存了的命令就这样一个个的再次执行了一遍,是不是很有意思呢?!
让我们分析一下程序执行的过程:
1、开始执行初始化界面,然后显示:
1 public static voidmain(String[] args) {2
3 new Main("命令模式");4
5 }
1 publicMain(String title){2 super(title);3
4 this.addWindowListener(this);5 canvas.addMouseMotionListener(this);6 btnClear.addActionListener(this);7 btnRePaint.addActionListener(this);8
9 Box btnBox=newBox(BoxLayout.X_AXIS);10 btnBox.add(btnClear);11 btnBox.add(btnRePaint);12
13 Box mainBox=newBox(BoxLayout.Y_AXIS);14 mainBox.add(btnBox);15 mainBox.add(canvas);16
17 getContentPane().add(mainBox);18
19 pack();20 show();21 }
2、然后等待用户的操作,当监听到用户在界面上拖动鼠标的时候,执行:
1 @Override2 public voidmouseDragged(MouseEvent e) {3 Command cmd=newDrawCommand(canvas,e.getPoint());4 history.append(cmd);5 cmd.execute();6 }
3、创建一个命令对象,然后记录进命令堆栈之中,之后我们跟踪cmd.execute();
1 packagezyr.dp.command;2
3 public interfaceCommand {4 public abstract void execute();5 }
4、这里就看到我们的面向抽象编程的好处了,根本不需要知道是谁执行了我们的命令,在命令的时候自然知道了,那就是newDrawCommand(canvas,e.getPoint());我们继续跟踪:
1 public class DrawCommand implementsCommand {2
3 。。。4 public voidexecute() {5 drawable.draw(position.x, position.y);6 }7
8 }
5、继续跟踪:
1 packagezyr.dp.command;2
3 public interfaceDrawable {4
5 public abstract void draw(int x,int y);6
7 }
6、同理,谁实现了Drawable,并被传递进去了,Command cmd=newDrawCommand(canvas,e.getPoint());
1 private DrawCanvas canvas=new DrawCanvas(400,400,history);
找到原主:DrawCanvas,跟踪:
1 public void draw(int x, inty) {2 Random random = newRandom();3
4 Graphics g =getGraphics();5 g.setColor((random.nextBoolean())?Color.yellow : Color.MAGENTA);6 g.fillOval(x-radius, y-radius, radius*2, radius*2);7 }8
因此执行我们的程序,画了一个点。之后我们的鼠标不断拖动着,这个流程就一直执行着,直到我们停止为止。
之后我们分析重画方法:
当用户点击按钮:
1 @Override2 public voidactionPerformed(ActionEvent e) {3 if(e.getSource()==btnClear){4 history.clear();5 canvas.repaint();6 }else if(e.getSource()==btnRePaint){7 canvas.repaint();8 }9 }
调用canvas.repaint();方法,这是Canvas自动实现的,我们不必深究,只需要知道这个函数之中会调用,我们的继承了Canvas并且重写的方法:
1 public voidpaint(Graphics g) {2 System.out.println("执行一次刷新!"+System.currentTimeMillis());3 history.execute();4 }
跟踪:history.execute();
1 public voidexecute() {2 Iterator it =commands.iterator();3 while(it.hasNext()){4 Command command=(Command)it.next();5 command.execute();6 }7 }
可以看到将保存的命令一个个都拿出来,重新走了一遍我们上面的command.execute();所走的流程,这就是命令模式,现在很清晰了。
三、总结
对于命令模式,在本例之中使用了Composite模式,迭代器等模式作为辅助,另外在生成对象的时候还可能使用原型模式,在保存命令的时候还可能使用备忘录模式。本例是一个很好的例子,从本质上说明了命令模式就是将命令抽象成一个类,通过保存接收者的引用,在后期还可以让接收者去执行,同样的使用了组合模式将这些对象一个个的保存了下来,然后一步步的调用单个命令的执行方法,该执行方法通知命令的接收者去再次执行命令,这种方式特别的方便,因为我们保存的是用户的操作,能够一直记录下来,甚至可以保存到文件之中以后可以恢复,由此可以看到命令模式的强大。