设计模式(二十二)Command模式

  一个类在进行工作时会调用自己或者是其他类的方法,虽然调用结果会反映在对象的状态中,但并不会留下工作的历史记录。

   这时,如果我们有一个类,用来表示“请进行这项工作”的“命令”就会方便很多。每一项想做的工作就不再是“方法的调用”这种动态处理了,而是一个表示命令的类的实例,即可以用“物”表示。要想管理工作的历史记录,只需管理这些实例的集合即可,而且还可以随时再次执行过去的命令,或是将多个过去的命令整合为一个新命令并执行。

  Command有时也被称为时间。当发生点击鼠标、按下键盘按键等事件时,我们可以先将这些事件作成实例,然后按照发生顺序放入队列中。接着,再依次去处理它们。

  

  示例程序要实现的功能是,用户拖动鼠标时程序会绘制出红色圆点,点击clear按钮后会清除所有的圆点。用户每拖动一次鼠标,应用程序都会为“在这个位置画一个点”这条命令生成一个DrawCommand类的实例。只要保存了这条命令,以后有需要时就可以重新绘制。

1 package bigjunoba.bjtu.command;
2 
3 public interface Command {
4     public abstract void execute();
5 }

  Command接口是表示“命令”的接口。作用就是“执行”什么东西。

package bigjunoba.bjtu.command;

import java.util.Stack;
import java.util.Iterator;

public class MacroCommand implements Command {
    // 命令的集合
    private Stack<Command> commands = new Stack<Command>();
    // 执行
    public void execute() {
        Iterator<Command> it = commands.iterator();
        while (it.hasNext()) {
            ((Command)it.next()).execute();
        }
    }
    // 添加命令,同时防止不小心将自己(this)添加进去。
    public void append(Command cmd) {
        if (cmd != this) {
            commands.push(cmd);
        }
    }
    // 删除push方法添加的最后一条命令
    public void undo() {
        if (!commands.empty()) {
            commands.pop();
        }
    }
    // 删除所有命令
    public void clear() {
        commands.clear();
    }
}

  MacroCommand类表示“由多条命令整合成的命令”。该类实现了Command接口。commands字段是Stack类型的,它是保存了多个Command(即实现了Command接口的实例)的集合。

 1 package bigjunoba.bjtu.drawer;
 2 
 3 import bigjunoba.bjtu.command.*;
 4 import java.awt.Point;
 5 
 6 public class DrawCommand implements Command {
 7     // 绘制对象
 8     protected Drawable drawable;
 9     // 绘制位置
10     private Point position;
11     // 构造函数
12     public DrawCommand(Drawable drawable, Point position) {
13         this.drawable = drawable;
14         this.position = position;
15     }
16     // 执行
17     public void execute() {
18         drawable.draw(position.x, position.y);
19     }
20 }

  DrawCommand类实现了Command接口,表示“绘制一个点的命令”。构造函数的作用是生成“在这个位置绘制点”的命令。execute方法的作用是执行命令。

1 package bigjunoba.bjtu.drawer;
2 
3 public interface Drawable {
4     public abstract void draw(int x, int y);
5 }

  Drawable接口是表示“绘制对象”的接口。draw方法是用于绘制的方法。

 1 package bigjunoba.bjtu.drawer;
 2 
 3 import bigjunoba.bjtu.command.*;
 4 import java.util.*;
 5 import java.awt.*;
 6 import java.awt.event.*;
 7 import javax.swing.*;
 8 
 9 public class DrawCanvas extends Canvas implements Drawable {
10     // 颜色
11     private Color color = Color.red;
12     // 要绘制的圆点的半径
13     private int radius = 6;
14     // 命令的历史记录
15     private MacroCommand history;
16     // 构造函数
17     public DrawCanvas(int width, int height, MacroCommand history) {
18         setSize(width, height);
19         setBackground(Color.white);
20         this.history = history;
21     }
22     // 重新全部绘制
23     public void paint(Graphics g) {
24         history.execute();
25     }
26     // 绘制
27     public void draw(int x, int y) {
28         Graphics g = getGraphics();
29         g.setColor(color);
30         g.fillOval(x - radius, y - radius, radius * 2, radius * 2);     //画圆点
31     }
32 }

  history字段中保存的是DrawCanvas类自己应当执行的绘制命令的集合。该字段是Command.MacroCommand类型的。当需要重新绘制DrawCanvas是,java处理会调用paint方法。它所做的事情仅仅是调用了history.excute方法。这样,记录在history中的所有历史命令都会被重新执行一遍。

 1 package bigjunoba.bjtu.test;
 2 
 3 import bigjunoba.bjtu.command.*;
 4 import bigjunoba.bjtu.drawer.*;
 5 
 6 import java.awt.*;
 7 import java.awt.event.*;
 8 import javax.swing.*;
 9 
10 public class Main extends JFrame implements ActionListener, MouseMotionListener, WindowListener {
11     // 绘制的历史记录
12     private MacroCommand history = new MacroCommand();
13     // 绘制区域
14     private DrawCanvas canvas = new DrawCanvas(400, 400, history);
15     // 删除按钮
16     private JButton clearButton  = new JButton("clear");
17 
18     // 构造函数
19     public Main(String title) {
20         super(title);
21 
22         this.addWindowListener(this);
23         canvas.addMouseMotionListener(this);
24         clearButton.addActionListener(this);
25 
26         Box buttonBox = new Box(BoxLayout.X_AXIS);
27         buttonBox.add(clearButton);
28         Box mainBox = new Box(BoxLayout.Y_AXIS);
29         mainBox.add(buttonBox);
30         mainBox.add(canvas);
31         getContentPane().add(mainBox);
32 
33         pack();
34         show();
35     }
36 
37     // ActionListener接口中的方法
38     public void actionPerformed(ActionEvent e) {
39         if (e.getSource() == clearButton) {
40             history.clear();
41             canvas.repaint();
42         }
43     }
44 
45     // MouseMotionListener接口中的方法
46     public void mouseMoved(MouseEvent e) {
47     }
48     public void mouseDragged(MouseEvent e) {
49         Command cmd = new DrawCommand(canvas, e.getPoint());
50         history.append(cmd);
51         cmd.execute();
52     }
53 
54     // WindowListener接口中的方法
55     public void windowClosing(WindowEvent e) {
56         System.exit(0);
57     }
58     public void windowActivated(WindowEvent e) {}
59     public void windowClosed(WindowEvent e) {}
60     public void windowDeactivated(WindowEvent e) {}
61     public void windowDeiconified(WindowEvent e) {}
62     public void windowIconified(WindowEvent e) {}
63     public void windowOpened(WindowEvent e) {}
64 
65     public static void main(String[] args) {
66         new Main("Command Pattern Sample");
67     }
68 }

  Main类实现了mouseDragged方法,当鼠标被拖动时,会生成一条“在这个位置画点”的命令,Command cmd = new DrawCommand(canvas, e.getPoint());该命令会先被添加至绘制历史记录中,history.append(cmd);然后立即执行,cmd.execute();

  测试结果如上图所示。

  示例程序的时序图。

  Command模式的类图。

 

posted @ 2018-04-12 11:29  BigJunOba  阅读(209)  评论(0编辑  收藏  举报