设计模式——06 命令模式

本文深入探讨了命令模式的设计理念,通过智能家居遥控器实例解析如何实现调用者与执行者解耦,支持可扩展与可撤销操作。文章详细介绍了命令模式的实现方式,包括命令对象、组合命令及撤销命令的设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

06 Command Pattern(命令模式)

         前言:封装调用,实现调用者和执行者的解耦。

需求分析:

         REQ1Vander的公司红红火火,接到的项目越来越多,还记得MS公司气象站项目吗, 没错这次又是他们找到了Vander,来请Vander设计一个创新控制器,这个控制器,或者可以说是遥控器,遥控器上有7个插槽(每个插槽对应一个家电,并且遥控器需要一个全局的撤销按钮),很明显,这次他们想做智能家居,他们同时送来了一个光盘,光盘里面各个类是各个家电的控制代码(如风扇/冰箱/热水器/空调等等)。他们希望Vander实现一组控制遥控器的API,让每个插槽都能够控制一个或者一组装置。下面是厂商给的类:

 实际上有许许多多的家用电器,都需要用遥控器控制,远不止以上这几个类。

遥控器具体是要接哪种类型的电器都不确定,通过无线模块来完成,所以遥控器得具有一定的可扩展性并且能适配所有的电器。

 分析:可能最直观的想法是直接使用一大堆if语句来完成,但是想想要先判断每个插槽是插入了哪个电器,然后再调用相应的电器进行相应的操作,假设现在的电器有几十种,这样的话维护代码具有很大难度,而且要经常改动。也不满足对修改关闭,对扩展开放的原则。下面让我们穿梭到餐厅,现在你到了香格里拉大酒店,你开始点餐,你点了法国鹅肝、美式牛扒还有一瓶82年的拉菲。你跟waiter说下单,然后waiter拿着你的菜单给厨师,厨师给你做菜。下面我们来分析这个过程:

 下面通过代码来实现这个部分,你会发现你将订单的执行者(厨师)和订单的创建者(顾客)解耦了。

订单:

public class SteakOrder implements Order {

	private Cooker cooker;
	
	public SteakOrder() {
		cooker = new Cooker();
	}
	
	public void orderUp() {
		cooker.cookSteak();
	}

}

服务员:

public class Waiter {

	private Order order;
	
	public void takeOrder(Order order) {
		this.order = order;
	}
	
	public void orderUp() {
		if(this.order != null) {
			order.orderUp();
		}
	}
	
}

厨师:

public class Cooker {

	public void cookSteak() {
		System.out.println("cooking delicious steak!");
	}
	
}

下面我们回到遥控器的设计上,遥控器实际上也是类似的,这些每个电器的开关都不一样,但是我们通过封装可以把它们统一起来,相当于打开电视、打开灯、打开冰箱、开热水器等这些动作就相当于厨师做各种各样的菜,遥控器这时候就充当了waiter的角色,遥控器并不需要知道有什么样的电器,它只需要知道这些连接它的电器都有excute方法就可以了,它只负责调用excute方法,然后由具体的电器来完成具体的命令。可能说得稍微有点抽象,来个图分析一下。

 

命令模式:将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。

我们知道一个命令对象通过在特定的接收者上绑定一组动作来封装一个请求。要达到这一点,命令对象将动作和接收者包进对象里面,这个对象只暴露了一个excute方法,当此方法被调用的时候,接收者就会进行这些动作。从外面来看,其他对象不知道究竟哪个接收者进行这些动作,只知道如果调用excute方法,请求的目的就能达到。

下面开始设计遥控器:

从上面类图可以看出,遥控器并不需要知道具体是哪个电器进行了什么样的操作,它只管pushButton的时候调用excute方法来实现具体的功能。

命令对象:

public class AirConditionOnCommand implements Command {

	private AirCondition airCondition;
	
	public AirConditionOnCommand(AirCondition airCondition) {
		this.airCondition = airCondition;
	}
	
	public void excute() {
		airCondition.setCool();
		airCondition.setTemperature(25);
		airCondition.on();
	}

}

 遥控器:

public class RemoteControl {
	
	private Command onButton[];
	
	private Command offButton[];
	
	public RemoteControl() {
		
		onButton = new Command[7];
		offButton = new Command[7];
		
		for(int i=0; i<onButton.length; i++) {
			onButton[i] = new NoCommand();
			offButton[i] = new NoCommand();
		}
		
	}
	
	public void setCommand(int slot, Command onCommand, Command offCommand) {
		onButton[slot] = onCommand;
		offButton[slot] = offCommand;
	}
	
	public void pushOnButton(int slot) {
		onButton[slot].excute();
	}
	
	public void pushOffButton(int slot) {
		offButton[slot].excute();
	}
	
}

 电器:

public class AirCondition {

	private int temperture;
	
	private boolean cool;
	
	public void on() {
		System.out.println("Aircondition is on with state is " + (cool? "cool":"warm")
			+ " and the temperature is " + temperture + "℃ !");
	}
	
	public void setCool() {
		this.cool = true;
	}
	
	public void setWarm() {
		this.cool =false;
	}
	
	public void setTemperature(int temp) {
		this.temperture = temp;
	}
	
	public void off() {
		System.out.println("Airconditon is off!");
	}
	
}

首先上面用了一个NoCommand,遥控器不可能一出厂就设置了有意义的命令对象,所以提供了NoCommand对象作为代用品,当调用它的excute方法时,这种对象什么事情都不做。当你不想返回一个有意义的对象时,空对象就很有用了。你也可以将处理null的责任转移给空对象。有时候空对象的使用也会当成一种设计模式。

下面我们进一步设计遥控器,首先遥控器上有多个按钮,上述的写法完成了开和关两个按钮,同样的,其余的按钮也可以这样依样画葫芦完成,但是每次要按那么多个按钮对应开启相应的电器还是麻烦了,能不能按一个按钮然后打开空调、灯、音响、热水器等呢,当然可以只需要建立一个CombineCommand对象即可,甚至你还可以建立一个UndoCommand命令来回退到刚刚的按钮状态。

家电:

public class AirCondition {

	private int temperture;
	
	private int preTemperature;
	
	private String state;
	
	private String preState;
	
	public void on() {
		System.out.println("Aircondition is on with state is " + state
			+ " and the temperature is " + temperture + "℃ !");
	}
	
	public void setState(String state) {
		preState = this.state;
		this.state = state;
	}

	public String getState() {
		return state;
	}

	public int getTemperture() {
		return temperture;
	}

	public void setTemperature(int temp) {
		preTemperature = this.temperture;
		this.temperture = temp;
	}
	
	public void off() {
		System.out.println("Airconditon is off!");
	}
	
	public void undo() {
		this.state = preState;
		this.temperture = preTemperature;
		System.out.println("Aircondition is on with state is " + state
				+ " and the temperature is " + temperture + "℃ !");
	}
	
}

 命令:

public class AirConditionOnCommand implements Command {

	private AirCondition airCondition;
	
	public AirConditionOnCommand(AirCondition airCondition) {
		this.airCondition = airCondition;
	}
	
	public void excute() {
		airCondition.setTemperature(airCondition.getTemperture());
		airCondition.setState(airCondition.getState());
		airCondition.on();
	}

	public void undo() {
		airCondition.off();
	}

}

组合命令:

public class CombineCommand implements Command {

	private Command[] commands;
	
	public CombineCommand(Command[] commands){
		this.commands = commands;
		for(int i=0; i<commands.length; i++) {
			if(commands[i] == null) {
				commands[i] = new NoCommand();
			}
		}
	}
	
	public void excute() {
		for(int i=0; i<commands.length; i++) {
			commands[i].excute();
		}
	}

	public void undo() {
		for(int i=0; i<commands.length; i++) {
			commands[i].undo();
		}
	}
	
}

 遥控器:

public class RemoteControl {
	
	private Command onButton[];
	
	private Command offButton[];
	
	private Command undoCommand;
	
	public RemoteControl() {
		
		onButton = new Command[7];
		offButton = new Command[7];
		
		for(int i=0; i<onButton.length; i++) {
			onButton[i] = new NoCommand();
			offButton[i] = new NoCommand();
		}
		undoCommand = new NoCommand();
	}
	
	public void setCommand(int slot, Command onCommand, Command offCommand) {
		onButton[slot] = onCommand;
		offButton[slot] = offCommand;
	}
	
	public void pushOnButton(int slot) {
		onButton[slot].excute();
		undoCommand = onButton[slot];
	}
	
	public void pushOffButton(int slot) {
		offButton[slot].excute();
		undoCommand = offButton[slot];
	}
	
	public void pushUndoButton() {
		undoCommand.undo();
	}
	
}

 

利用上面的思想我们还能实现一些炫酷的东西,例如队列,想想看,可以将不同的命令放入队列中,从队列拿命令出来的消费者并不需要知道拿出来的命令会执行什么操作,它只要去调用excute方法就可以了,调用完了再丢弃这个Command,继续获取下个Command。这样子工作队列类和进行具体计算操作的对象就完全是解耦的。

最后的最后,我们又来总结我们现在现有的设计模式武器。

面向对象基础

         抽象、封装、多态、继承

六大设计原则

设计原则一:封装变化

设计原则二:针对接口编程,不针对实现编程。

         设计原则三:多用组合,少用继承。

         设计原则四:为交互对象之间的松耦合设计而努力

设计原则五:对扩展开放,对修改关闭

设计原则六:依赖抽象,不要依赖于具体的类

模式

命令模式:将“请求”封装成对象,以便使用不同的请求,队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。

最后献上此次设计的源码,源码中包括了一些起初错误的实现,以及后期经过思考后正确的实现,在此处出现的所有源码均有实现,有需要的小伙伴可以下载来运行一下,首先先自己进行设计,然后再参考,这样才能加深命令模式的理解。

 

### Java 中命令模式(Command Pattern)的实现与示例 #### 1. 命令模式的核心概念 命令模式是一种行为型设计模式,其核心目标是将请求封装为一个对象,从而使不同类型的请求可以被参数化处理。这种方式不仅实现了请求发送者和接收者的解耦[^2],还提供了诸如撤销、重做以及日志记录等功能的支持。 #### 2. 核心角色及其职责 以下是命令模式的主要组成部分及其作用: - **命令接口(Command Interface)**: 定义了一个 `execute()` 方法用于执行命令,还可以定义 `undo()` 方法用于撤销命令。 - **具体命令(Concrete Command)**: 实现了命令接口,并绑定了具体的接收者与其操作方法。 - **接收者(Receiver)**: 执行实际的操作逻辑。 - **调用者(Invoker)**: 负责触发命令的执行,通常会持有某个命令对象的引用。 - **客户端(Client)**: 创建具体命令对象并将其与相应的接收者关联起来[^4]。 #### 3. 遥控器控制家电的经典示例 ##### 步骤 1:定义命令接口 ```java public interface Command { void execute(); } ``` ##### 步骤 2:实现接收者(家电类) 假设我们有一个简单的电灯作为接收者: ```java public class Light { public void turnOn() { System.out.println("Light is ON"); } public void turnOff() { System.out.println("Light is OFF"); } } ``` ##### 步骤 3:实现具体命令类 针对上述电灯的功能,分别创建打开和关闭的具体命令: ```java public class TurnOnLightCommand implements Command { private final Light light; public TurnOnLightCommand(Light light) { this.light = light; } @Override public void execute() { light.turnOn(); } } public class TurnOffLightCommand implements Command { private final Light light; public TurnOffLightCommand(Light light) { this.light = light; } @Override public void execute() { light.turnOff(); } } ``` ##### 步骤 4:实现调用者(遥控器) 遥控器作为一个调用者,负责存储当前的命令并通过按钮触发该命令: ```java public class RemoteControl { private Command command; public void setCommand(Command command) { this.command = command; } public void pressButton() { if (command != null) { command.execute(); } else { System.out.println("No command set."); } } } ``` ##### 步骤 5:客户端调用 最后,在客户端中完成整个流程的组装和测试: ```java public class Client { public static void main(String[] args) { // 接收者 Light livingRoomLight = new Light(); // 具体命令 Command onCommand = new TurnOnLightCommand(livingRoomLight); Command offCommand = new TurnOffLightCommand(livingRoomLight); // 调用者 RemoteControl remote = new RemoteControl(); // 设置并执行开灯命令 remote.setCommand(onCommand); remote.pressButton(); // 输出: Light is ON // 设置并执行关灯命令 remote.setCommand(offCommand); remote.pressButton(); // 输出: Light is OFF } } ``` #### 4. 支持宏命令(批量操作) 为了支持更复杂的场景,比如一次执行多个命令,可以通过引入宏命令来实现。下面是一个简单示例: ##### 步骤 1:定义宏命令类 ```java import java.util.ArrayList; import java.util.List; public class MacroCommand implements Command { private List<Command> commands = new ArrayList<>(); public void addCommand(Command command) { commands.add(command); } public void removeCommand(Command command) { commands.remove(command); } @Override public void execute() { for (Command cmd : commands) { cmd.execute(); } } } ``` ##### 步骤 2:客户端使用宏命令 ```java public class ClientWithMacro { public static void main(String[] args) { Light kitchenLight = new Light(); Light bedroomLight = new Light(); Command kitchenOn = new TurnOnLightCommand(kitchenLight); Command kitchenOff = new TurnOffLightCommand(kitchenLight); Command bedroomOn = new TurnOnLightCommand(bedroomLight); Command bedroomOff = new TurnOffLightCommand(bedroomLight); MacroCommand macroCommand = new MacroCommand(); macroCommand.addCommand(kitchenOn); macroCommand.addCommand(bedroomOn); RemoteControl remote = new RemoteControl(); remote.setCommand(macroCommand); remote.pressButton(); // 同时开启厨房和卧室灯光 } } ``` #### 5. 总结 通过以上实例可以看出,命令模式在分离请求发起方与执行方的同时,也增强了系统的灵活性和可维护性。然而需要注意的是,过多的命令可能会导致系统变得臃肿,因此应权衡利弊合理应用。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值