命令模式
1 命令模式简介
1.1 什么是命令模式
命令模式是一种行为设计模式,它可将请求转换成一个包含与请求相关的所有信息的独立对象。
该转化让你能根据不同的请求将方法参数化、延迟请求执行或将其放入队列中,且能实现可撤销操作。
1.2 命令模式的几个组成部分
-
Command
一个具体命令的抽象接口。
-
ConcreteCommand
命令接口实现对象,是“虚”的实现;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。 -
Receiver
作为具体命令的最终接收者。几乎任何对象都可以作为接收者,命令的细节由Receiver来实现。
-
Invoker
携带命令执行
action
请求。它不负责创建命令对象,它通常会通过构造函数从客户端处获得预先生成的命令。 -
Client
发起具体的命令交给Invoker去执行。
1.3 命令模式在开发中适用的情况:
1.系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
2.系统需要在不同的时间指定请求、将请求排队和执行请求。
3.系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
4.系统需要将一组操作组合在一起,即支持宏命令。
2 给定一个需求
需求: 对于不同的智能家电设备,这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个App,分别控制,我们希望只要一个App就可以控制全部智能家电. 要实现一个app控制所有智能家电的需要,则每个智能家电厂家都要提供一个统一的接口给app调用,在这里,动作的请求者是手机app,动作的执行者是每个厂商的一个家电产品.
2.1 一般的做法
1.首先我们需要定义几个接收请求的类,也就是我们需求中的家电类
我们这里定义智能电视以及电灯两个类,然后在里面定义一些响应请求的方法
//电灯类
public class Light {
public void turnOn(){
System.out.println("电灯打开成功");
}
public void turnOff(){
System.out.println("电灯关闭成功");
}
}
//智能电视类
public class Tv {
public void turnOn(){
System.out.println("智能电视开机");
}
public void turnOff(){
System.out.println("智能电视开机");
}
}
2.之后我们需要定义一个发布请求的手机类,里面需要有一些特点的请求方法,而且手机类需要持有他能控制的家电类的对象
//手机类
public class Phone {
Tv tv;
Light light;
public Phone(Tv tv,Light light){
this.tv=tv;
this.light=light;
}
public void openTv(){
this.tv.turnOn();
}
public void closeTv(){
this.tv.turnOff();
}
public void openLight(){
this.light.turnOn();
}
public void closeLight(){
this.light.turnOff();
}
}
3.然后我们可以进行测试
//测试类
public class Test {
public static void main(String[] args) {
Light light=new Light();
Tv tv=new Tv();
Phone phone=new Phone(tv,light);
phone.openLight();
phone.openTv();
}
}
在传统的模式下,我们的控制器和设备是强相关的,因为遥控器的代码在具体使用、调用的过程中用的功能是设备里面具体的执行的函数,所以如果一方变动另一方也要变动。
而我们项目开发过程,是对类的的扩展是开放的,但是对代码的修改是关闭的,也就是说,在扩展的过程中不能给老代码带来新的bug
2.2 命令模式
而命令模式的做法是将请求封装称为一个命令对象,调用者只要命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说 :“请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。
1.首先一样,我们先创建Receiver类,也就是家电类
我们这里使用一个Recevier接口,然后用家电类实现Recevier接口,方便管理。
//命令接收器接口
public interface Receiver {
//开机
public void trunOn();
//关机
public void trunOff();
}
智能电视类实现Recevier接口
public class Tv implements Receiver{
@Override
public void trunOn() {
System.out.println("智能电视开机");
}
@Override
public void trunOff() {
System.out.println("智能电视关机");
}
}
电灯类实现Recevier接口
public class Light implements Receiver{
@Override
public void trunOn() {
System.out.println("开灯");
}
@Override
public void trunOff() {
System.out.println("关灯");
}
}
2.然后我们再创建一个Command接口
//命令接口
public interface Command {
/**
* 执行操作
*/
public void execute();
/**
* 操作回退
*/
public void fallback();
}
3.之后再创建具体的请求的Command类
//关灯的命令类
public class LightOffCommand implements Command{
//关灯命令的接受者 也就是电灯
private Receiver receiver;
public LightOffCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.trunOff();
}
@Override
public void fallback() {
receiver.trunOn();
}
}
//开灯的命令类
public class LightOnCommand implements Command{
//开灯命令的接受者 也就是电灯
private Receiver receiver;
public LightOnCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.trunOn();
}
@Override
public void fallback() {
receiver.trunOff();
}
}
//电视开机的命令类
public class TvOffCommand implements Command{
//电视关机命令的接受者 也就是电视
public Receiver receiver;
public TvOffCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
this.receiver.trunOff();
}
@Override
public void fallback() {//也可以变成一个回退的函数
this.receiver.trunOn();
}
}
//电视关机的命令类
public class TvOnCommand implements Command{
//电视开机命令的接受者 也就是电视
public Receiver receiver;
public TvOnCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
this.receiver.trunOn();
}
@Override
public void fallback() {//也可以变成一个回退的函数
this.receiver.trunOff();
}
}
4.然后我们创建Invoker,在这里我们可以装在各种不同的Command类,载入命令类进入对列,之后可以进行命令的执行或者撤销/回退
//相等于一个手机类
public class Invoker {
// 打开 按钮的命令数组
private Command[] onCommands;
// 执行撤销的命令
private Command undoCommand;
/**
* 构造器,完成对按钮初始化
*/
public Invoker() {
onCommands = new Command[5];
for (int i = 0;i < 5; i++) {
onCommands[i] = new NullCommand();//初始化时,对按钮初始化了空命令,这样从数组中取命令时,就不用判断是否为空了
}
}
/**
* 给我们的按钮设置你需要的命令
* @param no
* @param onCommand
* @param //offCommand
*/
public void setCommand(int no, Command onCommand) {
onCommands[no] = onCommand;
}
/**
* 按下开按钮
* @param no
*/
public void onCommand(int no) {
// 找到你按下的开的按钮,并调用对应的方法
onCommands[no].execute();
// 记录这次的操作,用于撤销
undoCommand = onCommands[no];
}
/**
* 按下撤销按钮
*/
public void fallback() {
undoCommand.fallback();
}
}
5.最后我们可以建立一个Client类,给Invoke注入具体的Command类,以及你需要的Receiver类
//创建一个ConcreteCommand对象并指定他的Receiver对象
public class Client {
public static void main(String[] args) {
// 命令接收者Receiver
Tv myTv = new Tv();
// 开机命令ConcreteCommond
Command Tvon = new TvOnCommand(myTv);
// 关机命令ConcreteCommond
Command Tvoff = new TvOffCommand(myTv);
Light light=new Light();
Command Lighton = new LightOnCommand(light);
// 关机命令ConcreteCommond
Command Lightoff = new LightOffCommand(light);
Invoker invoke=new Invoker();
//命令可以装载入invoke,用的时候调用
//可以使用回调机制
//放入命令队列,慢慢处理(抢购)
invoke.setCommand(0,Tvon);
//可以实现命令的组合
invoke.onCommand(0);
invoke.fallback();
}
}
3.总结一下命令模式优缺点
3.1 优点
- 解耦。命令发起者和接收者不存在直接的引用,二者独立变化。
- 易扩展。增加新命令,不需要修改客户端代码。
- 易组合。可以通过命令与命令组合,创建更复杂的命令,比如一个批处理命令。
- 可以撤销和恢复。通过对命令的记录,可以撤销和恢复之前的命令。
3.2 缺点
- 类膨胀。一个具体的命令要对应一个命令类,会导致具体命令类变多。
借鉴:
https://blog.csdn.net/jason0539/article/details/45110355?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param
项目文件已上传到git:
https://github.com/coder-oyz/command_mode.git