设计模式——命令模式

设计模式——命令模式

基本介绍:

  • 命令模式(Command Pattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计;
  • 命名模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦;
  • 在命名模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作;
  • 通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色:将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)。Invoker 是调用者(将军),Receiver 是被调用者(士兵),MyCommand 是命令,实现了 Command 接口,持有接收对象;

细节和注意事项:

  • 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的 execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:”请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用;
  • 容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令;
  • 容易实现对请求的撤销和重做;
  • 命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在在使用的时候要注意;
  • 空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦;
  • 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟 CMD(DOS 命令)订单的撤销/恢复、触发- 反馈机制;

UML

在这里插入图片描述

编码

我们通过如下的一个问题来因此和如何实现命令模式:

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

通过上述的问题,我们使用访问者模式,先画出其UML(因为家电类型太多,所有只画取部分的家电),如下:
在这里插入图片描述
先来编写所有命令类的抽象接口MyCommand,如下:

package edu.hebeu.command.command;

/**
 * 所有命令类的接口
 * @author 13651
 *
 */
public interface MyCommand {
	
	void execute(); // 执行操作的命令
	
	void undo(); // 撤销操作的命令
}

有了上面的命令接口后,就使用该接口来创建出命令的调用者Invoker,如下:

package edu.hebeu.command.invoker;

import edu.hebeu.command.command.MyCommand;
import edu.hebeu.command.command.nullcommand.NullCommand;

/**
 * 这个类相当于 命令调用者
 * @author 13651
 *
 */
public class RemoteControllerInvoker {
	
	/**
	 * 存放各个电器的开命令,即每个电器个对应一个开按钮
	 */
	private MyCommand[] onButtons;
	
	/**
	 * 存放各个电器的关命令,即每个电器个对应一个关按钮
	 */
	private MyCommand[] offButtons;
	
	/**
	 * 用来存放最近一次执行的命令,以实现执行撤销的命令
	 */
	private MyCommand undoButton;
	
	/**
	 * 用来完成对按钮的初始化
	 */
	public RemoteControllerInvoker() {
		
		// 将开按钮的电器设置为5个
		onButtons = new MyCommand[5];
		// 将关按钮的电器设置为5个
		offButtons = new MyCommand[5];
		
		// 初始化所有的按钮为NullCommand类型,即默认不执行任何操作
		for(int i = 0; i < 5; i++) {
			onButtons[i] = new NullCommand();
			offButtons[i] = new NullCommand();
		}
		
	}
	
	/**
	 * 该方法用来设置命令
	 * @param index 哪个位置
	 * @param onCommand 开的命令
	 * @param offCommand 关的命令
	 */
	public void setCommand(int index, MyCommand onCommand, MyCommand offCommand) {
		onButtons[index] = onCommand;
		offButtons[index] = offCommand;
	}
	
	/**
	 * 如果点击的是on类型的按钮
	 * @param index 哪个位置的按钮
	 */
	public void onButtonPush(int index) {
		onButtons[index].execute();
		undoButton = onButtons[index]; // 记住这次的操作
	}
	
	/**
	 * 如果点击的是off类型的按钮
	 * @param index 哪个位置的按钮
	 */
	public void offButtonPush(int index) {
		offButtons[index].execute();
		undoButton = offButtons[index]; // 记住这次的操作
	}
	
	/**
	 * 如果点击撤销按钮
	 */
	public void undoButtonPush() {
		undoButton.undo();
	}
	
}

如果到这里你会发现上面的NullCommand类是什么?怎么会报错?没错这个类就是前面细节和注意事项中我们所说的为我们省去了判空的操作空命令NullCommandNullCommand的代码如下:

package edu.hebeu.command.command.nullcommand;

import edu.hebeu.command.command.MyCommand;

/**
 * 这个命令是一个空命令,其不论是执行命令操作的execute()方法,还是撤销命令操作的undo()方法都是默
 * 认实现,不会执行任何相关的操作,因此用来初始化命令,以此来省掉空判断;
 * @author 13651
 *
 */
public class NullCommand implements MyCommand{

	@Override
	public void execute() {
	}

	@Override
	public void undo() {
	}

}

现在我们的项目从总体来看已经有了:MyCommand命令接口(虽然还没有具体的命令)、RemoteControllerInvoker调用命令的类、以及为我们省去判空操作的NullCommand命令,我们此时最需要的应该就是被调用者Receiver(没有被调用者,我们的具体命令类去调谁也不知道不是?),这里我们仅仅做为演示就不创建那么多的Receiver家电类(被调用者)了,我们创建的家电类如下:
风扇FanReceiver

package edu.hebeu.command.receiver;

public class FanReceiver {
	public void on() {
		System.out.println("风扇开启...");
	}
	
	public void off() {
		System.out.println("风扇关闭...");
	}
}

电灯LightReceiver

package edu.hebeu.command.receiver;

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

电视TVReceiver

package edu.hebeu.command.receiver;

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

此时我们的家电类已经创建完毕(具体的被调用者已经有了),那么我们此时就应该创建这些被调用者的具体命令类,如下:
关闭电灯命令FanOffCommand

package edu.hebeu.command.command.fan;

import edu.hebeu.command.command.MyCommand;
import edu.hebeu.command.receiver.FanReceiver;

public class FanOffCommand implements MyCommand{
	
	private FanReceiver receiver;
	
	public FanOffCommand(FanReceiver receiver) {
		this.receiver = receiver;
	}

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

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

}

开启电灯命令FanOnCommand

package edu.hebeu.command.command.fan;

import edu.hebeu.command.command.MyCommand;
import edu.hebeu.command.receiver.FanReceiver;

public class FanOnCommand implements MyCommand{
	
	private FanReceiver receiver;
	
	public FanOnCommand(FanReceiver receiver) {
		this.receiver = receiver;
	}

	@Override
	public void execute() {
		receiver.on();
	}

	@Override
	public void undo() {
		receiver.off();
	}

}

关闭电灯命令LightOffCommand

package edu.hebeu.command.command.light;

import edu.hebeu.command.command.MyCommand;
import edu.hebeu.command.receiver.LightReceiver;

/**
 * 电灯关闭的命令
 * @author 13651
 *
 */
public class LightOffCommand implements MyCommand {
	
	private LightReceiver receiver;
	
	public LightOffCommand(LightReceiver receiver) {
		this.receiver = receiver;
	}

	/**
	 * 该命令对应的操作
	 */
	@Override
	public void execute() {
		receiver.off();
	}

	/**
	 * 该命令对应的撤销操作
	 */
	@Override
	public void undo() {
		receiver.on();
	}

}

开启电灯命令LightOnCommand

package edu.hebeu.command.command.light;

import edu.hebeu.command.command.MyCommand;
import edu.hebeu.command.receiver.LightReceiver;

/**
 * 电灯打开的命令
 * @author 13651
 *
 */
public class LightOnCommand implements MyCommand{
	
	private LightReceiver receiver;
	
	public LightOnCommand(LightReceiver receiver) {
		this.receiver = receiver;
	}

	/**
	 * 该命令对应的操作
	 */
	@Override
	public void execute() {
		receiver.on();
	}

	/**
	 * 该命令对应的撤销操作
	 */
	@Override
	public void undo() {
		receiver.off();
	}
	
	
}

关闭电视命令TVOffCommand

package edu.hebeu.command.command.tv;

import edu.hebeu.command.command.MyCommand;
import edu.hebeu.command.receiver.TVReceiver;

/**
 * 电视机关闭命令
 * @author 13651
 *
 */
public class TVOffCommand implements MyCommand {
	
	private TVReceiver receiver;
	
	public TVOffCommand(TVReceiver receiver) {
		this.receiver = receiver;
	}

	/**
	 * 该命令对应的操作
	 */
	@Override
	public void execute() {
		receiver.off();
	}

	/**
	 * 该命令对应的撤销操作
	 */
	@Override
	public void undo() {
		receiver.on();
	}
}

开启电视命令TVOnCommand

package edu.hebeu.command.command.tv;

import edu.hebeu.command.command.MyCommand;
import edu.hebeu.command.receiver.TVReceiver;

/**
 * 电视机打开命令
 * @author 13651
 *
 */
public class TVOnCommand implements MyCommand {
	
	private TVReceiver receiver;
	
	public TVOnCommand(TVReceiver receiver) {
		this.receiver = receiver;
	}

	/**
	 * 该命令对应的操作
	 */
	@Override
	public void execute() {
		receiver.on();
	}

	/**
	 * 该命令对应的撤销操作
	 */
	@Override
	public void undo() {
		receiver.off();
	}

}

测试类的编写

为了更好的测试,我们编写如下测试类:

package edu.hebeu.command;

import java.util.Scanner;

import edu.hebeu.command.command.fan.FanOffCommand;
import edu.hebeu.command.command.fan.FanOnCommand;
import edu.hebeu.command.command.light.LightOffCommand;
import edu.hebeu.command.command.light.LightOnCommand;
import edu.hebeu.command.command.tv.TVOffCommand;
import edu.hebeu.command.command.tv.TVOnCommand;
import edu.hebeu.command.invoker.RemoteControllerInvoker;
import edu.hebeu.command.receiver.LightReceiver;
import edu.hebeu.command.receiver.FanReceiver;
import edu.hebeu.command.receiver.TVReceiver;

public class Client {
	private static Scanner SCANNER = new Scanner(System.in);
	
	public static void main(String[] args) {
		RemoteControllerInvoker remoteControllerInvoker = new RemoteControllerInvoker();
		// 给0号索引的按钮位置设置电灯的开关
		remoteControllerInvoker.setCommand(0, 
				new LightOnCommand(new LightReceiver()), 
				new LightOffCommand(new LightReceiver()));
		// 给1号索引的按钮位置设置电视机的开关
		remoteControllerInvoker.setCommand(1, 
				new TVOnCommand(new TVReceiver()), 
				new TVOffCommand(new TVReceiver()));
		// 给2号索引的位置设置声音的大小开关
		remoteControllerInvoker.setCommand(2, 
				new FanOnCommand(new FanReceiver()), 
				new FanOffCommand(new FanReceiver()));
		
		while(true) {
			System.out.println();System.out.println();
			System.out.println("--------按钮列表---------");
			System.out.println("开灯:lo");
			System.out.println("关灯:lc");
			System.out.println("开电视:to");
			System.out.println("关电视:tc");
			System.out.println("开风扇:fo");
			System.out.println("关风扇:fc");
			System.out.println("返回:back");
			
			String keyword = SCANNER.next();
			
			switch(keyword) {
				case "lo":
					remoteControllerInvoker.onButtonPush(0); // 打开电灯命令
					break;
				case "lc":
					remoteControllerInvoker.offButtonPush(0); // 关闭电灯命令
					break;
				case "to":
					remoteControllerInvoker.onButtonPush(1); // 打开电视命令
					break;
				case "tc":
					remoteControllerInvoker.offButtonPush(1); // 关闭电视命令
					break;
				case "fo":
					remoteControllerInvoker.onButtonPush(2); // 打开风扇命令
					break;
				case "fc":
					remoteControllerInvoker.offButtonPush(2); // 关闭风扇命令
					break;
				case "back":
					remoteControllerInvoker.undoButtonPush(); // 撤回命令
					break;
			}
		}
		
	}
}

进行测试

在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值