1、引入
很多情况下,我们操作一个对象,往往通过对象所在的类中的方法来执行的。
然而这样的使用方式在部分情况下是复杂的(一个类中有许多执行操作方法),并且无法支持操作历史记录的需求。
这时候引入Command模式:
创建一个或几个类,来专门表示对对象的操作。这些类被称为指令类,有了指令类,我们可以通过其提供的指令来操作对象
在Command模式中,有以下角色:
- 被操作类
- 指令类
- 请求操作者,操作发动者
2、示例
实现以下功能,提供GUI界面,用户拖动鼠标时会画下图像。并且系统会保存用户的指令,以便以后使用(重绘等,案例中并未实现)。
2.1、指令类
2.1.1、指令接口
在此接口仅提供了一个方法,当需要更多方法时拓展即可
public interface Command {
public abstract void execute();
}
2.1.2、复合指令MacroCommand
在此类中,实现了对历史记录的储存,并且可以批量执行历史记录
public class MacroCommand implements Command{
private Stack<Command> commands=new Stack<>();
@Override
public void execute() {
Iterator<Command> iterator = commands.iterator();
while (iterator.hasNext()) {
iterator.next().execute();
}
}
public void append(Command cmd) {
if (cmd != this) {
commands.push(cmd);
}
}
public void undo() {
if (!commands.empty()) {
commands.pop();
}
}
public void clear() {
commands.clear();
}
}
2.1.3、对绘制命令的实现DrawCommand
public class DrawCommand implements Command{
protected Drawable drawable;
private Point position;
public DrawCommand(Drawable drawable, Point position) {
this.drawable = drawable;
this.position = position;
}
@Override
public void execute() {
drawable.draw(position.x,position.y);
}
}
2.2、被操作者
2.2.1、
由于实现的是绘图功能,因此被操作者应有Drawble接口
public interface Drawable {
public abstract void draw(int x,int y);
}
2.2.2、
在被操作者内,要提供对指令类的接口(draw()方法)
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() {
history.execute();
}
@Override
public void draw(int x, int y) {
Graphics graphics = getGraphics();
graphics.setColor(color);
graphics.fillOval(x-radius, y-radius, radius*2, radius*2);
}
}
2.3、调用者
创建一个GUI界面,在其上应用指令类提供的绘制指令
这里对GUI的创建可以不必在意细节(如果不是客户端同学的话),理解指令类是如何被调用的即可
public class Main extends JFrame implements ActionListener, MouseMotionListener, WindowListener {
//历史记录
private MacroCommand history=new MacroCommand();
//绘图区
private DrawCanvas canvas=new DrawCanvas(400, 400, history);
//删除键
private JButton clearButton=new JButton("clear");
public Main(String title) {
super(title);
this.addWindowListener(this);
canvas.addMouseMotionListener(this);
clearButton.addActionListener(this);
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();
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == clearButton) {
history.clear();
canvas.repaint();
}
}
@Override
public void mouseDragged(MouseEvent e) {
DrawCommand cmd = new DrawCommand(canvas, e.getPoint());
history.append(cmd);
cmd.execute();
}
@Override
public void mouseMoved(MouseEvent e) {
}
@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");
}
}
运行上程序,可以有下结果:
需要指出的是,限于篇幅在本例中并没有对历史记录这一功能的体现,仅仅体现了指令类对图像的操作。
3、tips
-
指令类的实现需要被操作对象暴露出接口以供指令类调用,通过指令类间接操作。这点类似于Facade模式,然而两者存在不同。
- Facade模式强调的是聚合复杂繁多的指令,提供对外友好的接口,在本例中为draw()方法
- Command模式强调的是将操作通过指令类实现,将指令视为独立于类的“物”。这点可以从Facade模式往往是被操作类中提供接口,Command是将指令独立为类,前者是依赖于被操作对象实例的,后者则可以独立与被操作对象。
-
指令类的存在使得代码结构清晰了许多,将被操作对象和操作分开存放,使得阅读和使用更加清晰,并且降低了耦合性。
-
对被操作类使用统一接口可以使得Command类能够重复利用,在新的图形界面中如果实现了Drawble接口,仍然可以用原来的Command类操作(例子中并未体现)。