》》表示命令的类的实例,即可以用"物"来表示。要想管理工作的历史记录,
只需管理这些实例的集合即可,而且还可以随时再次执行过去的命令,
或是将过去的命令整合为一个新的命令并执行。
》》Command 有时也称为事件(event) ,它与“事件驱动程序”中的
“事件“是一样的意思。当发生点击鼠标、按下键盘按键等事件时,我们
可以先将这些事件作成实例,然后按照发生顺序放入队列中。接着,再
依次去处理它们。在GUI 编程中,经常需要与”事件“打交道。
-------------------下面的示例程序是一个画图软件,它的功能很简单,即用户
拖动鼠标时程序会绘制出红色圆点,点击 clear 按钮后会清除所有的
圆点
》》运行的结果:
》》示例程序的类图:
》》Command 接口:
package command.command;
/*
* 该接口是表示“命令”的接口,该接口的作用是:“执行”什么东西
*/
public interface Command {
public abstract void execute();
}
》》MacroCommand 类:
package command.command;
import java.util.Iterator;
import java.util.Stack;
/*
* 该类表示“由多条命令整合成的命令”
*/
public class MacroCommand implements Command {
//命令的集合
private Stack commands = new Stack();
//执行
public void execute(){
//获取该栈的迭代器
Iterator it=commands.iterator();
while(it.hasNext()){
((Command)it.next()).execute();
}
}
//添加命令
public void append(Command cmd){
/*
* if 语句的作用是防止不小心将自己(this)添加进去。
* 如果这么做了,execute 方法将会陷入循环,永远不停地执行。
*/
if(cmd != this){
commands.push(cmd);
}
}
//删除最后一条命令
public void undo(){
if(!commands.empty()){
commands.pop();
}
}
//删除所有命令
public void clear(){
commands.clear();
}
}
》》DrawCommand 类:
package command.drawer;
import java.awt.Point;
import command.command.Command;
/*
* 该类表示“绘制一个点的命令”
*
* Point类表示由 X轴 和 Y轴构成的平面上的坐标。
*/
public class DrawCommand implements Command {
//绘制对象
protected Drawable drawable;
//绘制位置
private Point position;
//构造函数
public DrawCommand(Drawable drawable , Point position){
this.drawable = drawable;
this.position = position;
}
//执行
public void execute(){
drawable.draw(position.x, position.y);
}
}
》》Drawable 接口:
package command.drawer;
/*
* 该接口表示“绘制对象”的接口,draw 方法是用于绘制的方法
*/
public interface Drawable {
public abstract void draw(int x ,int y);
}
》》DrawCanvas 类:
package command.drawer;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import command.command.MacroCommand;
/*
* 该类继承 Canvas ,Canvas 相当于一个 画布
*/
public class DrawCanvas extends Canvas implements Drawable{
//当前画笔的颜色
private Color color = Color.red;
//要绘制的圆点的半径
private int radius = 6;
//命令的历史记录
private MacroCommand history;
//构造函数
public DrawCanvas(int width , int height , MacroCommand history){
//设置画布的宽和高
setSize(width , height);
//设置画布的背景色
setBackground(Color.white);
this.history = history;
}
//重新全部绘制
public void paint(Graphics g){
history.execute();
}
//绘制
public void draw(int x ,int y){
//在该画布中获取画笔
Graphics g = getGraphics();
//设置该画笔的颜色
g.setColor(color);
//画圆点
g.fillOval(x-radius, y-radius, radius*2, radius*2);
}
}
》》Main 类:
补充:在该类的构造函数中设置了用于接收鼠标按下等事件的监听器,
并安排了各个控件(组件)在界面中的布局。
在javax.swing.JFrame 中,必须将控件放置在通过getContentPane
方法获取的容器之内。
package command.drawer;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.Box;
import java.awt.Point;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import command.command.Command;
import command.command.MacroCommand;
public class Main extends JFrame implements ActionListener,WindowListener,
MouseMotionListener{
//绘制的历史记录
private MacroCommand history = new MacroCommand();
//绘制区域
private DrawCanvas canvas = new DrawCanvas(400,400,history);
//删除按钮
private JButton clearButton = new JButton("clear");
//构造函数
public Main(String title){
/*
* 调用父类 JFrame 里面构造函数 public JFrame(String title)
throws HeadlessException
*/
super(title);
/*
* 对下面添加监听器时传入的参数的理解:该参数应该是实现了监听器(接口)里面的
* 方法的实例的引用
*/
//给当前窗口对象添加 窗口监听器
this.addWindowListener(this);
//给按钮添加 事件监听器
clearButton.addActionListener(this);
//给画布添加鼠标移动监听器
canvas.addMouseMotionListener(this);
//在JFrame里面添加组件
Box buttonBox = new Box(BoxLayout.X_AXIS);
buttonBox.add(clearButton);
Box mainBox = new Box(BoxLayout.Y_AXIS);
mainBox.add(buttonBox);
mainBox.add(canvas);
getContentPane().add(mainBox);
//调整此窗口的大小,以适合其子组件的首选大小和布局。
pack();
//代替 show() 显示出来
setVisible(true);
}
//java.awt.event.ActionListener 接口里面的方法,发生操作时
//会调用下面的方法
public void actionPerformed(ActionEvent e){
//getSource():获取最初发生 Event 的对象
if(e.getSource() == clearButton ){
//清除所有的命令
history.clear();
//重绘此组件。
canvas.repaint();
}
}
/*
*java.awt.event.WindowListener 接口里面的方法
*/
//将 Window 设置为活动 Window 时调用。只有框架或对话框可以成为活动 Window。
public void windowActivated(WindowEvent e){
}
//窗口首次变为可见时调用。
public void windowOpened(WindowEvent e){
}
//用户试图从窗口的系统菜单中关闭窗口时调用。
public void windowClosing(WindowEvent e){
System.exit(0);
}
//因对窗口调用 dispose 而将其关闭时调用。
public void windowClosed(WindowEvent e){
}
//窗口从正常状态变为最小化状态时调用。
public void windowIconified(WindowEvent e){
}
//窗口从最小化状态变为正常状态时调用。
public void windowDeiconified(WindowEvent e){
}
//当 Window 不再是活动 Window 时调用。
public void windowDeactivated(WindowEvent e){
}
/*
* java.awt.event.MouseMotionListener
* 接口中的方法
*/
//鼠标按键在组件上按下并拖动时调用。
public void mouseDragged(MouseEvent e){
//e.getPoint() 返回鼠标当前位置(已经按下)相对于源组件的 x, y 坐标。
Command cmd = new DrawCommand(canvas , e.getPoint());
history.append(cmd);
cmd.execute();
}
//鼠标光标移动到组件上但无按键按下时调用。
public void mouseMoved(MouseEvent e){
}
public static void main(String[] args){
new Main("Command Pattern Sample");
}
}
--------------------------------------
《控件布局》
-----------------------------------------------------------------------
《Command 模式中的登场角色》
*** Command ( 命令)
Command 角色负责定义命令的接口。在示例程序中,由 Command 接口扮演此角色。
*** ConcreteCommand (具体的命令)
ConcreteCommand 角色负责实现在 Command 角色中定义的接口。在示例程序中,由
MacroCommand 类 和 DrawCommand 类扮演此角色。
*** Receiver (接收者)
Receiver 角色是 Command 角色执行命令时的对象,也可以称其为命令接收者。在示例
程序中 由 DrawCanvas 类接收 DrawCommand 的命令。
*** Client (请求者)
Client 角色负责生成 ConcreteCommand 角色并分配 Receiver 角色。在示例程序中,由
Main 类扮演此角色。在响应鼠标拖拽事件时,它生成了 DrawCommand 类的实例,并
将扮演 Receiver 角色的 DrawCanvas 类的实例传递给了DrawCommand 类的构造函数。
*** Invoker (发动者)
Invoker 角色是开始执行命令的角色,它会调用在Command 角色中定义的接口。在示例
程序中,由 Main 类和 DrawCanvas 类扮演此角色。这两个类都调用了 Command 接口中
的 execute 方法。Main 类同时扮演了 Client 角色 和 Invoker 角色。
----------------------------------------------------------------------------------------
《扩展思路的要点》
1.命令中应该包含哪些信息
****命令的目的不同,应该包含的信息也不同。
**** 在上面示例中,DrawCommand 类中的 drawable 字段表示的是绘制对象(具体的
命令接收者)。
*** 在 ConcreteCommand 角色中只要知道自己的 Receiver 角色,不论谁来管理或持有
ConcreteCommand 角色,都可以执行 execute 方法的。
2.保存历史记录
*** 在Main 类中,MacroCommand 类的实例(history)代表了绘制的历史记录。在该
字段中保存了之前所有的绘制信息。也就是说,如果我们将它保存为文件,就可以永久
保存历史记录。
3.适配器
***在 Main 类中,我们实现了 3 个接口:ActionListener 、WindowListener 、
MouseMotionListener 。这三个接口下面的方法,都需要进行实现(即使是空的方法)
有的方法调用了,有的方法没有调用。
*** 为了简化程序,java.awt.event 提供了一些被称为适配器(Adapter)的类。
**** 在适配器中已经实现了接口中所定义的所有方法。不过,所有的实现都是空
(即什么都不做)的。因此,我们只要编写一个 适配器的子类,然后实现所需要的
方法即可,而不必在意其他不需要的方法。
**** 特别是把 java 匿名内部类与适配器结合起来使用时,可以更轻松地编写程序。
**** 在编译匿名内部类时,生成的类文件的名字会像下面这样,其命名规则是:
“主类名 $ 编号.class”
eg: Main$1.class
-------------------------------------------------------------------------
《相关的设计模式》
**** Composite 模式
**** Memento 模式
**** Prototype 模式