您现在的位置是:首页 >技术教程 >设计模式之【命令模式】,方法调用的花式玩法网站首页技术教程

设计模式之【命令模式】,方法调用的花式玩法

秃了也弱了。 2024-06-17 10:31:55
简介设计模式之【命令模式】,方法调用的花式玩法

一、什么是命令模式

命令模式(Command Pattern)是对命令的封装,每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行操作。命令模式解耦了请求方和接收方,请求方只需请求执行命令,不用关心命令是怎样被接受,怎样被操作以及是否被执行等。命令模式属于行为型模式。

原文:将一个请求封装成医德对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。

在软件系统中,行为请求者与行为实现者通常是一种紧耦合关系,因为这样的实现简单明了。但紧耦合关系缺乏扩展性,在某些场合中,当需要为行为进行记录,撤销或重做等处理时,只能修改源码。而命令模式通过为请求与实现间引入了一个抽象命令接口,解耦了请求与实现,并且中间件是抽象的,它可以有不同的子类实现,因此其具备扩展性。所以,命令模式的本质是解耦命令请求与处理。

1、命令模式使用场景

当系统的某项操作具备命令语义时,且命令实现不稳定(变化),那么可以通过命令模式解耦请求与实现,利用抽象命令接口使请求方代码架构稳定,封装接收方具体命令实现细节。接收方与抽象命令接口呈现弱耦合(内部方法无需一致),具备良好的扩展性。命令模式适用于以下应用场景:

  • 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
  • 现实语义中具备“命令”的操作(如命令菜单、shell命令等)。
  • 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
  • 系统需要在不同的时间指定请求、将请求排队和执行请求。
  • 需要支持命令宏(即命令组合操作)。

2、命令模式的主要角色

在这里插入图片描述
命令模式包含以下主要角色:

  • 抽象命令类(Command)角色: 定义命令的接口,声明执行的方法。
  • 具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
  • 实现者/接收者(Receiver)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
  • 调用者/请求者(Invoker)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。

从命令模式的UML类图中可以看出:Command的出现就是作为Receiver和Invoker的中间件,解耦了彼此。而之所以引入Command中间件,主要有以下两方面的原因:
(1)解耦请求与实现:即解耦了Invoker和Receiver,因为在UML类图中,Invoker是一个具体的实现,等待接收客户端传入命令(即Invoker与客户端耦合),Invoker处于业务逻辑区域,应当是一个稳定的结构。而Receiver是属于业务功能模块,是经常变动的;如果没有Command,则Invoker紧耦合Receiver,一个稳定的结构依赖了一个不稳定的结构,就会导致整个结构都不稳定了。这也就是Command引入的原因:不仅仅是解耦请求与实现,同时稳定(Invoker)依赖文档(Command),结构还是稳定的。
(2)扩展性增强:扩展性体现在两个方面:① Receiver属于底层细节,可以通过更换不同的Receiver达到不同的细节实现;② Command接口本身就是抽象的,本身就具备扩展性,而且由于命令对象本身就具备抽象,如果结合装饰器模式,功能扩展简直如鱼得水。

注!在一个系统中,不同的命令对应不同的请求,也就是说无法把请求抽象化,因此命令模式中的Receiver是具体实现;但是如果在某一个模块中,可以对Receiver进行抽象,其实这就变相使用到了桥接模式(Command类具备两个变化的维度:Command和Receiver),这样子的扩展性会更加优秀。

3、命令模式优缺点

优点:

  • 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
  • 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
  • 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
  • 方便实现 Undo 和 Redo 操作。命令模式可以与备忘录模式结合,实现命令的撤销与恢复。
  • 可以在现有命令的基础上,增加额外功能(比如日志记录等等,结合装饰器模式效果更佳)。

缺点:

  • 使用命令模式可能会导致某些系统有过多的具体命令类。
  • 命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构,解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难(不过这也是设计模式带来的一个通病,抽象必然会引入额外类型;抽象肯定比紧密难理解)。
  • 使用频率低、理解难度大,只在非常特定的应用场景下才会用到。

4、命令模式注意事项及细节

  • 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:“请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。
  • 容易设计一个命令队列,只要把命令对象放到队列,就可以多线程的执行命令。
  • 容易实现对请求的撤销和重做。
  • 空命令也是一种设计模式,它为我们省去了判空的操作。可以使用接口适配器模式,将Command接口适配为一个空类。

二、使用示例

1、命令模式的一般写法

//抽象命令接口
public interface ICommand {
    void execute();
}

//具体命令
public class ConcreteCommand implements ICommand {
    // 直接创建接收者,不暴露给客户端
    private Receiver mReceiver;

    public ConcreteCommand(Receiver mReceiver) {
        this.mReceiver = mReceiver;
    }

    public void execute() {
        this.mReceiver.action();
    }
}
//请求者
public class Invoker {
    private ICommand mCmd;

    public Invoker(ICommand cmd) {
        this.mCmd = cmd;
    }

    public void action() {
        this.mCmd.execute();
    }
}
//接收者
public class Receiver {
    public void action() {
        System.out.println("执行具体操作");
    }
}
public class Test {
    public static void main(String[] args) {
        ICommand cmd = new ConcreteCommand(new Receiver());
        Invoker invoker = new Invoker(cmd);
        invoker.action();
    }
}

2、播放器功能案例

假如我们自己开发一个播放器,播放器有播放功能、拖拽进度条功能、停止播放功能、暂停播放功能,我们自己去操作播放器的时候并不是直接调用播放器的方法,而是通过一个控制条去传达指令给播放器内核,那么具体传达什么指令,会被封装为一个个的按钮。那么每个按钮就相当于是对一条命令的封装。用控制条实现了用户发送指令与播放器内核接收指令的解耦。

// Receiver角色:播放器内核GPlayer
public class GPlayer {
    public void play(){
        System.out.println("正常播放");
    }

    public void speed(){
        System.out.println("拖动进度条");
    }

    public void stop(){
        System.out.println("停止播放");
    }

    public void pause(){
        System.out.println("暂停播放");
    }
}

// 抽象命令Command角色:命令接口
public interface IAction {
    void execute();
}

// 具体命令角色
public class StopAction implements IAction {
    private GPlayer gplayer;

    public StopAction(GPlayer gplayer) {
        this.gplayer = gplayer;
    }

    public void execute() {
        gplayer.stop();
    }
}

public class PauseAction implements IAction {
    private GPlayer gplayer;

    public PauseAction(GPlayer gplayer) {
        this.gplayer = gplayer;
    }

    public void execute() {
        gplayer.pause();
    }
}

public class PlayAction implements IAction {
    private GPlayer gplayer;

    public PlayAction(GPlayer gplayer) {
        this.gplayer = gplayer;
    }

    public void execute() {
        gplayer.play();
    }
}

public class SpeedAction implements IAction {
    private GPlayer gplayer;

    public SpeedAction(GPlayer gplayer) {
        this.gplayer = gplayer;
    }

    public void execute() {
        gplayer.speed();
    }
}

// Invoker 角色
public class Controller {
    private List<IAction> actions = new ArrayList<IAction>();

    public void addAction(IAction action){
        actions.add(action);
    }

    public void execute(IAction action){
        action.execute();
    }

    public void executes(){
        for (IAction action:actions) {
            action.execute();
        }
        actions.clear();
    }
}
public class Test {
    public static void main(String[] args) {

        GPlayer player = new GPlayer();
        Controller controller = new Controller();
        controller.execute(new PlayAction(player));

        controller.addAction(new PauseAction(player));
        controller.addAction(new PlayAction(player));
        controller.addAction(new StopAction(player));
        controller.addAction(new SpeedAction(player));
        controller.executes();
    }
}

3、遥控器案例

买了一套智能家电,有点灯、风扇、冰箱、洗衣机、电视机,我们只需要在手机上安装app就可以控制这些家电。

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

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

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

	@Override
	public void execute() {
	}

	@Override
	public void undo() {
	}
}

// 电灯Receiver
public class LightReceiver {

	public void on() {
		System.out.println(" 电灯打开了.. ");
	}
	
	public void off() {
		System.out.println(" 电灯关闭了.. ");
	}
}
// 电灯具体命令类
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 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();
	}
}

// 电视机Receiver
public class TVReceiver {
	
	public void on() {
		System.out.println(" 电视机打开了.. ");
	}
	
	public void off() {
		System.out.println(" 电视机关闭了.. ");
	}
}
// 电视机具体命令
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
	TVReceiver tv;

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

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

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

// 控制器,Invoker角色
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) {
		
		//使用命令设计模式,完成通过遥控器,对电灯的操作
		
		//创建电灯的对象(接受者)
		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、Thread

Runable是一个典型命令模式,Runnable担当命令的角色,Thread充当的是调用者,start方法就是其执行方法。

//命令接口(抽象命令角色)
public interface Runnable {
	public abstract void run();
}
//调用者
public class Thread implements Runnable {
	private Runnable target;
	public synchronized void start() {
		if (threadStatus != 0)
			throw new IllegalThreadStateException();
		group.add(this);
		boolean started = false;
		try {
			start0();
			started = true;
		} finally {
		try {
			if (!started) {
				group.threadStartFailed(this);
			}
			} catch (Throwable ignore) {
			}
		}
	}
	private native void start0();
}

会调用一个native方法start0(),调用系统方法,开启一个线程。而接收者是对程序员开放的,可以自己定义接收者。

/**
* jdk Runnable 命令模式
* TurnOffThread : 属于具体
*/
public class TurnOffThread implements Runnable{
	private Receiver receiver;
	public TurnOffThread(Receiver receiver) {
		this.receiver = receiver;
	}
	public void run() {
		receiver.turnOFF();
	}
}
/**
* 测试类
*/
public class Demo {
	public static void main(String[] args) {
		Receiver receiver = new Receiver();
		TurnOffThread turnOffThread = new TurnOffThread(receiver);
		Thread thread = new Thread(turnOffThread);
		thread.start();
	}
}

实际上调用线程的start方法之后,就有资格去抢CPU资源,而不需要我们自己编写获得CPU资源的逻辑。而线程抢到CPU资源后,就会执行run方法中的内容,用Runnable接口把用户请求和CPU执行进行了解耦。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。