将命令视为类-Command模式

131 阅读3分钟

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");
    }
}

运行上程序,可以有下结果:

image.png 需要指出的是,限于篇幅在本例中并没有对历史记录这一功能的体现,仅仅体现了指令类对图像的操作。

3、tips

  • 指令类的实现需要被操作对象暴露出接口以供指令类调用,通过指令类间接操作。这点类似于Facade模式,然而两者存在不同。

    • Facade模式强调的是聚合复杂繁多的指令,提供对外友好的接口,在本例中为draw()方法
    • Command模式强调的是将操作通过指令类实现,将指令视为独立于类的“物”。这点可以从Facade模式往往是被操作类中提供接口,Command是将指令独立为类,前者是依赖于被操作对象实例的,后者则可以独立与被操作对象。
  • 指令类的存在使得代码结构清晰了许多,将被操作对象和操作分开存放,使得阅读和使用更加清晰,并且降低了耦合性。

    image.png

  • 对被操作类使用统一接口可以使得Command类能够重复利用,在新的图形界面中如果实现了Drawble接口,仍然可以用原来的Command类操作(例子中并未体现)。