设计模式_命令模式_Command

案例引入

  • 有一套智能家电,其中有照明灯、风扇、冰箱、洗衣机,这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个手机App来分别控制,希望只要一个app就可以控制全部智能家电
  • 要实现一个app控制所有智能家电的需要,则每个智能家电厂家都要提供一个统一的接口给app调用,这样可以考虑使用命令模式
  • 命令模式可将“动作的请求者”从“动作的执行者”对象中解耦出来
  • 在我们的例子中,动作的请求者是手机app,动作的执行者是每个厂商的一个家电产品

介绍

基础介绍

  • 一个类在进行工作时会调用自己或是其他类的方法,虽然调用结果会反映在对象的状态中,但并不会留下工作的历史记录。这时,如果有一个类用来表示“请示进行这项工作”的“命令”就会方便很多。每一项想做的工作就不再是“方法的调用”这种动态处理了,而是一个表示命令的类的实例,即可以用“物”来表示。要想管理工作的历史记录,只需管理这些实例的集合即可,而且还可以随时再次执行过去的命令,或是将多个过去的命令整合为一个新命令并执行。这样的模式称为命令模式
  • 命令模式使得请求发送者请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦,我们只需在程序运行时指定具体的请求接收者即可
  • 在命令模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命令),同时命令模式也支持可撤销的操作
  • 通俗易懂的理解:将军发布命令,士兵去执行(将军只需要发布一个进攻命令即可,不需要指定哪位士兵要做什么,士兵就会各司其职)。其中有几个角色:将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)

登场角色

  • Command(命令):负责定义命令的接口(API)
  • ConcreteCommand(具体的命令):ConcreteCommand角色负责实现在Command角色中定义的接口(API)。聚合接收者,将一个接收者对象与一个动作绑定,调用接收者相应的操作来实现execute
  • Receiver(接收者):Receiver角色是执行命令的对象,知道如何实施和执行一个请求对应的操作
  • Client(请求者):Client角色负责生成ConcreteCommand角色并分配Receiver角色
  • Invoker(发动者):调用在Command 角色中定义的接口(API)

在这里插入图片描述

案例实现

案例一

在这里插入图片描述

实现

【Commond】

package com.test.command;

/**
* 创建命令接口
*/
public interface Command {

/**
* 执行动作(操作)
*/
public void execute();

/**
* 撤销动作(操作)
*/
public void undo();
}

【电灯命令执行者】

package com.test.command;

/**
* 真正执行者
*/
public class LightReceiver {

public void on() {
System.out.println(" 电灯打开了.. ");
}

public void off() {
System.out.println(" 电灯关闭了.. ");
}
}

【关灯命令】

package com.test.command;

public class LightOffCommand implements Command {

/**
* 聚合LightReceiver
*/
LightReceiver light;

/**
* 构造器
* @param light
*/
public LightOffCommand(LightReceiver light) {
super();
this.light = light;
}

@Override
public void execute() {
// 调用接收者的方法
light.off();
}

@Override
public void undo() {
// 调用接收者的方法
light.on();
}
}

【开灯命令】

package com.test.command;

public class LightOnCommand implements Command {

/**
* 聚合LightReceiver
*/
LightReceiver light;

/**
* 构造器
*
* @param light
*/
public LightOnCommand(LightReceiver light) {
super();
this.light = light;
}

@Override
public void execute() {
//调用接收者的方法
light.on();
}

@Override
public void undo() {
//调用接收者的方法
light.off();
}

}

【电视命令执行者】

package com.test.command;

public class TVReceiver {

public void on() {
System.out.println(" 电视机打开了.. ");
}

public void off() {
System.out.println(" 电视机关闭了.. ");
}
}

【关电视命令】

package com.test.command;

public class TVOffCommand implements Command {

/**
* 聚合TVReceiver
*/
TVReceiver tv;

/**
* 构造器
*
* @param tv
*/
public TVOffCommand(TVReceiver tv) {
super();
this.tv = tv;
}

@Override
public void execute() {
// 调用接收者的方法
tv.off();
}

@Override
public void undo() {
// 调用接收者的方法
tv.on();
}
}

【开电视命令】

package com.test.command;

public class TVOnCommand implements Command {

/**
* 聚合TVReceiver
*/
TVReceiver tv;

/**
* 构造器
*
* @param tv
*/
public TVOnCommand(TVReceiver tv) {
super();
this.tv = tv;
}

@Override
public void execute() {
// 调用接收者的方法
tv.on();
}

@Override
public void undo() {
// 调用接收者的方法
tv.off();
}
}

【空命令】

package com.test.command;

/**
* 没有任何命令,即空执行: 用于初始化每个按钮, 当调用空命令时,对象什么都不做
* 其实,这样是一种设计模式, 可以省掉对空判断
* @author Administrator
*
*/
public class NoCommand implements Command {

@Override
public void execute() {

}

@Override
public void undo() {

}

} 

【遥控器】

package com.test.command;

public class RemoteController {

/**
* 开 按钮的命令数组
*/
Command[] onCommands;
Command[] offCommands;

/**
* 执行撤销的命令,必须要记住上一次执行的命令对应的撤销命令才能撤销
*/
Command undoCommand;

/**
* 构造器,完成对按钮初始化
*/
public RemoteController() {
onCommands = new Command[5];
offCommands = new Command[5];

// 初始化五组命令,初始化为空命令
for (int i = 0; i < 5; i++) {
onCommands[i] = new NoCommand();
offCommands[i] = new NoCommand();
}
}

/**
* 给我们的按钮设置你需要的命令
*
* @param no 命令编号
* @param onCommand
* @param offCommand
*/
public void setCommand(int no, Command onCommand, Command offCommand) {
onCommands[no] = onCommand;
offCommands[no] = offCommand;
}

/**
* 按下开按钮
*
* @param no
*/
public void onButtonWasPushed(int no) {
// 找到你按下的开的按钮, 并调用对应方法
onCommands[no].execute();
// 记录这次的操作,用于撤销
undoCommand = onCommands[no];
}

/**
* 按下开按钮
*
* @param no
*/
public void offButtonWasPushed(int no) {
// 找到你按下的关的按钮, 并调用对应方法
offCommands[no].execute();
// 记录这次的操作,用于撤销
undoCommand = offCommands[no];
}

/**
* 按下撤销按钮
*/
public void undoButtonWasPushed() {
undoCommand.undo();
}

}

【客户端】

package com.test.command;

public class Client {

public static void main(String[] args) {

//使用命令设计模式,完成通过遥控器,对电灯的操作

//创建电灯的对象(接受者)
LightReceiver lightReceiver = new LightReceiver();

//创建电灯相关的开关命令
LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver);
LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver);

//需要一个遥控器
RemoteController remoteController = new RemoteController();

//给我们的遥控器设置命令, 比如 no = 0 是电灯的开和关的操作
remoteController.setCommand(0, lightOnCommand, lightOffCommand);

System.out.println("--------按下灯的开按钮-----------");
remoteController.onButtonWasPushed(0);
System.out.println("--------按下灯的关按钮-----------");
remoteController.offButtonWasPushed(0);
System.out.println("--------按下撤销按钮-----------");
remoteController.undoButtonWasPushed();


System.out.println("=========使用遥控器操作电视机==========");

TVReceiver tvReceiver = new TVReceiver();

TVOffCommand tvOffCommand = new TVOffCommand(tvReceiver);
TVOnCommand tvOnCommand = new TVOnCommand(tvReceiver);

//给我们的遥控器设置命令, 比如 no = 1 是电视机的开和关的操作
remoteController.setCommand(1, tvOnCommand, tvOffCommand);

System.out.println("--------按下电视机的开按钮-----------");
remoteController.onButtonWasPushed(1);
System.out.println("--------按下电视机的关按钮-----------");
remoteController.offButtonWasPushed(1);
System.out.println("--------按下撤销按钮-----------");
remoteController.undoButtonWasPushed();

}

}

【运行】

--------按下灯的开按钮-----------
电灯打开了.. 
--------按下灯的关按钮-----------
电灯关闭了.. 
--------按下撤销按钮-----------
电灯打开了.. 
=========使用遥控器操作电视机==========
--------按下电视机的开按钮-----------
电视机打开了.. 
--------按下电视机的关按钮-----------
电视机关闭了.. 
--------按下撤销按钮-----------
电视机打开了.. 

Process finished with exit code 0

案例二

介绍

这段示例程序是一个画图软件,它的功能很简单,即用户拖动鼠标时程序会绘制出红色圆点,点击 clear 按钮后会清除所有的圆点。

在这里插入图片描述

实现

在这里插入图片描述

【命令接口】

package com.test.command.Sample.command;

public interface Command {
/**
* 执行
*/
public abstract void execute();
}
12345678

【历史命令集:由多条命令整合成的命令】

如果保存这个实例,就可以永久保存历史数据

package com.test.command.Sample.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();
}
}

/**
* 添加命令
*
* @param cmd
*/
public void append(Command cmd) {
if (cmd != this) {
// 判断不是自己再添加进去,不然会死循环
commands.push(cmd);
}
}

/**
* 删除最后一条命令
*/
public void undo() {
if (!commands.empty()) {
// 取出最后添加到栈的命令
commands.pop();
}
}

/**
* 删除所有命令
*/
public void clear() {
commands.clear();
}
}

【绘制点命令】

package com.test.command.Sample.drawer;


import com.test.command.Sample.command.Command;
import java.awt.*;

public class DrawCommand implements Command {

/**
* 绘制对象
*/
protected Drawable drawable;

/**
* 绘制位置 Point 是 java.awt 包的类,含有 (x,y) 坐标
*/
private Point position;

/**
* 构造函数
*
* @param drawable
* @param position
*/
public DrawCommand(Drawable drawable, Point position) {
this.drawable = drawable;
this.position = position;
}

/**
* 执行
*/
public void execute() {
drawable.draw(position.x, position.y);
}
}

【绘制对象接口】

package com.test.command.Sample.drawer;

public interface Drawable {
public abstract void draw(int x, int y);
}
12345

【绘图类】

package com.test.command.Sample.drawer;

import com.test.command.Sample.command.MacroCommand;

import java.awt.*;

/**
* 继承Canvas
*/
public class DrawCanvas extends Canvas implements Drawable {
/**
* 颜色
*/
private Color color = Color.red;
/**
* 要绘制的圆点的半径
*/
private int radius = 6;
/**
* 命令的历史记录
*/
private MacroCommand history;

/**
* 构造函数
*
* @param width
* @param height
* @param history
*/
public DrawCanvas(int width, int height, MacroCommand history) {
// 设置画布尺寸
setSize(width, height);
// 设置画布颜色
setBackground(Color.white);
this.history = history;
}

/**
* 重新全部绘制
* @param g   the specified Graphics context
*/
public void paint(Graphics g) {
history.execute();
}

/**
* 绘制
* @param x
* @param y
*/
public void draw(int x, int y) {
Graphics g = getGraphics();
// 设置笔画颜色
g.setColor(color);
// 绘制圆点
g.fillOval(x - radius, y - radius, radius * 2, radius * 2);
}
}

【主类】

package com.test.command.Sample;

import com.test.command.Sample.command.*;
import com.test.command.Sample.drawer.*;

import javax.swing.*;
import java.awt.event.*;

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

/**
* 构造函数
* @param title
*/
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();
}

/**
* ActionListener接口中的方法
* @param e
*/
public void actionPerformed(ActionEvent e) {
if (e.getSource() == clearButton) {
// 清空历史命令
history.clear();
// 清空画布
canvas.repaint();
}
}

public void mouseMoved(MouseEvent e) {
}

/**
*  MouseMotionListener接口中的方法
* @param e
*/
public void mouseDragged(MouseEvent e) {
// 鼠标位置一改变,就创建绘制点命令放到命令集合中
Command cmd = new DrawCommand(canvas, e.getPoint());
// 执行绘制
cmd.execute();
history.append(cmd);
}

/**
*  WindowListener接口中的方法
* @param e
*/
public void windowClosing(WindowEvent e) {
System.exit(0);
}
public void windowActivated(WindowEvent e) {}
public void windowClosed(WindowEvent e) {}
public void windowDeactivated(WindowEvent e) {}
public void windowDeiconified(WindowEvent e) {}
public void windowIconified(WindowEvent e) {}
public void windowOpened(WindowEvent e) {}

public static void main(String[] args) {
new Main("Command Pattern Sample");
}
}

在这里插入图片描述

【运行】

在这里插入图片描述

拓展

如何示例程序中增加“设置颜色”的功能。

【颜色命令】

package com.test.command.A1.drawer;


import com.test.command.A1.command.Command;

import java.awt.*;

public class ColorCommand implements Command {
/**
* 绘制对象
*/
protected Drawable drawable;
/**
* 颜色
*/
private Color color;

/**
* 构造函数
*
* @param drawable
* @param color
*/
public ColorCommand(Drawable drawable, Color color) {
this.drawable = drawable;
this.color = color;
}

/**
* 执行
* 设置画笔的颜色
*/
public void execute() {
drawable.setColor(color);
}
}

【主类】

package com.test.command.A1;


import com.test.command.A1.command.Command;
import com.test.command.A1.command.MacroCommand;
import com.test.command.A1.drawer.ColorCommand;
import com.test.command.A1.drawer.DrawCanvas;
import com.test.command.A1.drawer.DrawCommand;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

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");
/**
* 红色按钮
*/
private JButton redButton = new JButton("red");
/**
* 绿色按钮
*/
private JButton greenButton = new JButton("green");
/**
* 蓝色按钮
*/
private JButton blueButton = new JButton("blue");

/**
* 构造函数
*
* @param title
*/
public Main(String title) {
super(title);

this.addWindowListener(this);
canvas.addMouseMotionListener(this);
clearButton.addActionListener(this);
redButton.addActionListener(this);
greenButton.addActionListener(this);
blueButton.addActionListener(this);

Box buttonBox = new Box(BoxLayout.X_AXIS);
buttonBox.add(clearButton);
buttonBox.add(redButton);
buttonBox.add(greenButton);
buttonBox.add(blueButton);
Box mainBox = new Box(BoxLayout.Y_AXIS);
mainBox.add(buttonBox);
mainBox.add(canvas);
getContentPane().add(mainBox);

pack();
show();
}

/**
* ActionListener接口中的方法
* 点击不同颜色的按钮,设置不同颜色画笔
*
* @param e
*/
public void actionPerformed(ActionEvent e) {
if (e.getSource() == clearButton) {
history.clear();
canvas.init();
canvas.repaint();
} else if (e.getSource() == redButton) {
Command cmd = new ColorCommand(canvas, Color.red);
history.append(cmd);
cmd.execute();
} else if (e.getSource() == greenButton) {
Command cmd = new ColorCommand(canvas, Color.green);
history.append(cmd);
cmd.execute();
} else if (e.getSource() == blueButton) {
Command cmd = new ColorCommand(canvas, Color.blue);
history.append(cmd);
cmd.execute();
}
}

/**
* MouseMotionListener接口中的方法
*
* @param e
*/
public void mouseMoved(MouseEvent e) {
}

public void mouseDragged(MouseEvent e) {
Command cmd = new DrawCommand(canvas, e.getPoint());
history.append(cmd);
cmd.execute();
}

/**
* WindowListener接口中的方法
*
* @param e
*/
public void windowClosing(WindowEvent e) {
System.exit(0);
}

public void windowActivated(WindowEvent e) {
}

public void windowClosed(WindowEvent e) {
}

public void windowDeactivated(WindowEvent e) {
}

public void windowDeiconified(WindowEvent e) {
}

public void windowIconified(WindowEvent e) {
}

public void windowOpened(WindowEvent e) {
}

public static void main(String[] args) {
new Main("Command Pattern Sample");
}
}

【运行】

在这里插入图片描述

命令模式在JdbcTemplate源码中的应用

应用模式和标准的命令模式有点区别,但是精髓是差不多的

在这里插入图片描述

在这里插入图片描述

还有其他的命令执行者

在这里插入图片描述

在这里插入图片描述

总结

【优点】

  • 发起请求的对象执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的。命令对象(就是具体的命令)会负责让接收者执行调用者请求的动作,也就是说:”请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用
  • 命令模式容易设计一个命令队列。只要把命令对象放到队列,就可以多线程地执行命令。不仅如此,还容易将命令执行的历史纪录保存起来
  • 命令模式容易实现对请求的撤销和重做
  • 空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦(声明命令数组时,首先将数组的元素全部初始化为空命令)

【缺点】

  • 可能导致某些系统有过多的具体命令类,增加了系统的复杂度

你可能感兴趣的:(设计模式,设计模式,命令模式)