设计模式(十五)——命令模式(Command Pattern)

命令模式(Command Pattern)

基本介绍

  1. 命令模式:将一一个请求封装为一一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。也称之为:动作Action模式、事务transaction模式。
  2. 命名模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。
  3. 在命名模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作。
  4. 通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色:将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)。

什么时候使用命令模式

命令模式(Command Pattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计

命令模式的UML类图

在这里插入图片描述

角色分析

Command抽象命令类

命令角色,需要执行的所有命令都在这里,可以是接口或抽象类

ConcreteCommand具体命令类

将一个接受者对象与一个动作绑定,调用接受者相应的操作,实现 execute

Invoker调用者/请求者

请求的发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联。在程序运行时,将调用命令对象的execute() ,间接调用接收者的相关操作。

Receiver接收者(命令的真正的执行者)

接收者执行与请求相关的操作,具体实现对请求的业务处理。(未抽象前,实际执行操作内容的对象)。

Client客户类

在客户类中需要创建调用者对象、具体命令类对象,在创建具体命令对象时指定对应的接收者。发送者和接收者之间没有直接关系,都通过命令象间接调用。

代码实现

Command抽象命令类

public interface Command {
	/**
	 * 这个方法是一个返回结果为空的方法。
	 * 实际项目中,可以根据需求设计多个不同的方法
	 */
	void execute();

	//这里也可以定义撤销方法,使得命令模式更加严谨
}

ConcreteCommand具体命令类

聚合命令的真正的执行者(可以在命令真正执行前或后,执行相关的处理)

class ConcreteCommand implements Command {

	//聚合命令的真正的执行者
	private Receiver receiver;
	
	public ConcreteCommand(Receiver receiver) {
		this.receiver = receiver;
	}

	@Override
	public void execute() {
		//这里也可以在命令真正执行前或后,执行相关的处理!
		receiver.action();
	}
	
}

Receiver接收者(命令的真正的执行者)

public class Receiver {
	public void action(){
		System.out.println("Receiver.action()");
	}
}

Invoker调用者/请求者

public class Invoke {

	//也可以通过容器List<Command>容纳很多命令对象,进行批处理。数据库底层的事务管理就是类似的结构!
	private Command command;   

	public Invoke(Command command) {
		super();
		this.command = command;
	} 
	
	//业务方法 ,用于调用命令类的方法
	public void call(){
		command.execute();
	}
	
}

Client客户类

public class Client {
	public static void main(String[] args) {
		Command c = new ConcreteCommand(new Receiver());
		Invoke i = new Invoke(c);
		i.call();
	
		//传统的实现方式
//		new Receiver().action();
		
	}
}

结果

Receiver.action()

开发中常见的场景:

  • Struts2中,action的整个调用过程中就有命令模式。
  • 数据库事务机制的底层实现
  • 命令的撤销和恢复
  • Spring 框架的 JdbcTemplate 使用到了命令模式

实例分析

智能生活项目

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

命令模式实现智能生活项目

UML类图

在这里插入图片描述

代码实现

抽象命令接口

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

	//执行动作(操作)
	void execute();
	//撤销动作(操作)
	void undo();
	
}

现在实现两个开关分别是电视开关和灯开关

命令接收者(实际执行者)

灯命令接收者

public class LightReceiver {

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

电视命令接收者

public class TVReceiver {
	
	public void on() {
		System.out.println(" 电视机打开了.. ");
	}
	
	public void off() {
		System.out.println(" 电视机关闭了.. ");
	}
}

具体命令类
灯开关命令

public class LightOnCommand implements Command {

	//聚合LightReceiver
	LightReceiver light;
	
	//构造器
	public LightOnCommand(LightReceiver light) {
		super();
		this.light = light;
	}
	
	@Override
	public void execute() {
		//调用接收者的方法
		light.on();
	}

	

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

}
public class LightOffCommand implements Command {

	// 聚合LightReceiver
	LightReceiver light;

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

	@Override
	public void execute() {
		light.off();
	}

	@Override
	public void undo() {
		light.on();
	}
}

电视机开关命令

public class TVOnCommand implements Command {

	// 聚合TVReceiver
	TVReceiver tv;

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

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

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

	TVReceiver tv;
	
	public TVOffCommand(TVReceiver tv) {
		super();
		this.tv = tv;
	}

	@Override
	public void execute() {
		tv.off();
	}

	@Override
	public void undo() {
		tv.on();
	}
}

空命令
按钮初始化的时候,直接初始化空命令的按钮,之后使用,只需要设置按钮的状态即可。

空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。

public class NoCommand implements Command {

	@Override
	public void execute() {
		
	}

	@Override
	public void undo() {
		
	}

}

调用者/请求命令者

遥控器

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

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

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

	}

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

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

}

客户端调用

public class Client {

	public static void main(String[] args) {
		
		//使用命令设计模式,完成通过遥控器,对电灯的操作
		System.out.println("=========使用遥控器操作电视机==========");
		
		//创建电灯的对象(接受者)
		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();

	}

}

结果

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

命令模式的注意事项和细节

空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。

优点

  1. 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的 execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:”请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。
  2. 容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令
  3. 容易实现对请求的撤销和重做

缺点

  1. 命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在在使用的时候要注意
  2. 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟 CMD(DOS 命令)订单的撤销/恢复、触发- 反馈机制
发布了52 篇原创文章 · 获赞 68 · 访问量 7145

猜你喜欢

转载自blog.csdn.net/qq_42937522/article/details/105125921