1. 定义
在软件系统中,行为请求者与行为实现者通常呈现一种“紧耦合”。但在某些场合,比如要对行为进行“记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,实现二者之间的松耦合。这就是命令模式(Command Pattern)。[引自百度百科]
定义概括:
1. 行为请求者与行为实现者解耦
2. 行为抽象为对象
2.类图
Command模式中几种角色:
Command(命令): 负责定义命令的接口
ConcreteCommand(具体的命令):实现 Command接口的类,其中也持有了Receiver角色的引用。
Receiver(接收者): 是Command角色执行命令时的对象,是真正执行命令的对象,在Command命令的execute()方法里被调用。
Invoker(发动者):开始执行命令的角色,它会调用Command角色中定义的接口。
Client(请求者): 负责生成ConcreteCommand实例并将Receiver角色分配给ConcreteCommand(设置命令对象的接收者)。它不是常规意义的客户端,而是组装了ConcreteCommand 角色和Receiver角色。
3. 例子
举个例子说明一下吧。
编写一个简单的画图软件,用户拖动鼠标时绘制红点,点击clear按钮清除所有绘制的轨迹。
首先定义抽象的命令接口:
public interface Command {
void execute();
}
定义Drawable接口:
public interface Drawable {
void draw(int x, int y);
}
定义一个绘制一个点的具体命令DrawCommand:
import java.util.Iterator;
import java.util.Stack;
public class DrawCommand implements Command {
protected Drawable drawable; //Receiver角色,真正执行命令的角色
private Point position;
//该构造方法被Client角色调用,将具体的Receiver角色传入进来
public DrawCommand(Drawable drawable, Point position) {
this.drawable = drawable;
this.position = position;
}
@Override
public void execute() {
//Receiver角色执行
drawable.draw(position.x, position.y);
}
}
定义MacroCommand类管理所有的命令集合:
public class MacroCommand implements Command {
//命令的集合
private Stack<Command> commands = new Stack<>();
@Override
public void execute() {
Iterator<Command> iter = commands.iterator();
while (iter.hasNext()) {
iter.next().execute();
}
}
//添加一条命令
public void append(Command command) {
if (command != this) {
commands.push(command);
}
}
//清除所有命令
public void clear() {
commands.clear();
}
//撤销一条命令
public void undo() {
if(!commands.isEmpty()) {
commands.pop();
}
}
}
定制化一个简单的Canvas类:
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
public class DrawCanvas extends Canvas implements Drawable, MouseMotionListener {
private Color color = Color.red;
private int radius = 3;
private MacroCommand history;
public DrawCanvas(int width, int height, MacroCommand command) {
setSize(width, height);
setBackground(Color.GRAY);
this.history = command;
this.addMouseMotionListener(this);
}
@Override
public void paint(Graphics g) {
super.paint(g);
history.execute();
}
@Override
public void draw(int x, int y) {
Graphics g = getGraphics();
g.setColor(color);
g.fillOval(x, y, radius, radius);
}
//鼠标在Cancas上拖动时,绘制图形
@Override
public void mouseDragged(MouseEvent e) {
Command cmd = new DrawCommand(this, e.getPoint());
history.append(cmd);
cmd.execute();
}
@Override
public void mouseMoved(MouseEvent e) {
}
}
定义一个简单的UI类:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Main extends JFrame implements ActionListener, WindowListener {
private MacroCommand histroy = new MacroCommand();
private DrawCanvas canvas = new DrawCanvas(400, 400, histroy);
private JButton clearButton = new JButton("clear");
private JButton undoButton = new JButton("undo");
public Main(String title) {
super(title);
this.addWindowListener(this);
clearButton.addActionListener(this);
undoButton.addActionListener(this);
Box buttonBox = new Box(BoxLayout.X_AXIS);
buttonBox.add(clearButton);
buttonBox.add(undoButton);
canvas.setBackground(Color.GRAY);
Box mainBox = new Box(BoxLayout.Y_AXIS);
mainBox.add(canvas);
mainBox.add(buttonBox);
getContentPane().add(mainBox);
pack();
show();
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == clearButton) {
histroy.clear();
canvas.repaint();
}
else if(e.getSource() == undoButton) {
histroy.undo();
canvas.repaint();
}
}
@Override
public void windowOpened(WindowEvent e) {
}
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
@Override
public void windowClosed(WindowEvent e) {
}
@Override
public void windowIconified(WindowEvent e) {
}
@Override
public void windowDeiconified(WindowEvent e) {
}
@Override
public void windowActivated(WindowEvent e) {
}
@Override
public void windowDeactivated(WindowEvent e) {
}
public static void main(String[] args) {
new Main("Command Pattern Sample");
}
}
看个效果,虽然有点丑陋:
示例角色分析:
- DrawableCancas充当Invoker角色和Client角色,因为它存了MacroCommand, 并在mouseDragged方法中调用了command的execute角色。
- DrawCommand和MacroCommand充当ConcreteCommand角色
- DrawCanvas也充当了receiver角色。
4. 总结
命令模式特点:
- 行为请求者(Invoker)与行为实现者(Receiver)分离, 媒介是Command角色。 (个人理解)。
- 降低耦合性,但是可能会产生较多的命令类。
- JDK的Runnable与Thread就运用了命令模式,Thread是Invoker角色, Runnable是command角色,但是我们在实现Runnable接口时直接替换了Receiver角色。