命令模式是一种行为设计模式,它将请求封装为对象以实现客户端参数化、请求排队、日志记录及撤销操作,旨在解耦调用者与操作实现者,以智能家居为例,用户通过界面发送命令对象,设备作为接收者执行相应操作,无需用户了解具体执行方式,从而增强了系统的灵活性和可扩展性。
定义
命令模式是一种行为设计模式,它允许将一个请求封装为一个对象,从而可用不同的请求把客户端参数化,对请求排队或记录请求日志,以及支持可撤销的操作,主要目的是将调用操作的对象与知道如何实现该操作的对象解耦。
举一个业务中的例子来说明命令模式:假如有一个智能家居系统,其中有各种设备如灯光、空调、窗帘等,用户可以通过手机应用、语音助手或墙壁开关来控制这些设备,在这个场景中,命令模式可以很好地应用,在命令模式中有如下几个角色和分工:
- 命令对象:每个设备操作(如“打开灯光”、“关闭空调”)都可以封装为一个命令对象,这个命令对象包含了执行该操作所需的所有信息,包括目标设备、操作类型等。
- 调用者:用户或用户通过的界面(如手机应用)是调用者,他们不知道具体如何执行某个操作,但他们可以创建和发送命令对象。
- 接收者:实际执行操作的设备(如灯光设备、空调设备)是接收者,它们知道如何响应特定的命令。
- 撤销操作:命令模式还支持撤销操作,例如,如果用户误操作打开了灯光,他们可以发送一个“关闭灯光”的命令来撤销之前的操作。
这个例子中,用户(调用者)只需要发送命令,而不需要知道如何执行这些命令,设备(接收者)则负责根据接收到的命令执行相应的操作,这使得系统更加灵活和可扩展。
代码案例
日常开发中,未使用命令模式时,代码通常会直接将调用者和接收者紧密耦合在一起,下面是一个简单的反例,展示了未使用命令模式时如何实现一个智能家居系统中的灯光控制功能。首先,定义一个Light
类作为接收者,它负责执行打开和关闭灯光的操作,如下代码:
// 灯光类,作为接收者
public class Light { private boolean isOn; public Light() { this.isOn = false; } // 打开灯光 public void turnOn() { this.isOn = true; System.out.println("Light is on."); } // 关闭灯光 public void turnOff() { this.isOn = false; System.out.println("Light is off."); } // 检查灯光状态 public boolean isOn() { return isOn; }
}
接下来,定义一个SmartHomeController
类作为调用者,在这个类中,直接调用了Light
对象的方法,如下代码:
// 智能家居控制器类,作为调用者
public class SmartHomeController { private Light light; public SmartHomeController(Light light) { this.light = light; } // 控制灯光打开 public void controlLightOn() { light.turnOn(); } // 控制灯光关闭 public void controlLightOff() { light.turnOff(); }
}
最后,在client代码中使用SmartHomeController
来控制灯光的打开和关闭,如下代码:
// 客户端代码
public class Client { public static void main(String[] args) { // 创建灯光对象 Light light = new Light(); // 创建智能家居控制器对象,并将灯光对象传递给它 SmartHomeController controller = new SmartHomeController(light); // 控制灯光打开 controller.controlLightOn(); // 控制灯光关闭 controller.controlLightOff(); // 如果需要添加撤销操作或者记录操作日志,这种直接调用的方式将会变得复杂 }
}
输出结果:
Light is on.
Light is off.
在这个反例中,SmartHomeController
直接调用了Light
对象的方法,这意味着如果需要添加额外的功能,比如记录操作日志,则需要在SmartHomeController
中添加相应的逻辑,这会增加代码的复杂性,并且违反了开闭原则(对扩展开放,对修改关闭),此外,如果需要替换灯光控制的实现,比如使用远程服务器来控制灯光,也需要修改SmartHomeController
的代码。
下面是一个使用命令模式的正例代码,展示了如何实现一个智能家居系统中的灯光控制功能,当使用命令模式时,可以将请求封装为对象,并在调用者和接收者之间引入一个间接层,首先,定义一个Light
类作为接收者,它负责执行打开和关闭灯光的操作,如下代码:
// 灯光类,作为接收者
public class Light { private boolean isOn; public Light() { this.isOn = false; } // 打开灯光 public void turnOn() { this.isOn = true; System.out.println("Light is on."); } // 关闭灯光 public void turnOff() { this.isOn = false; System.out.println("Light is off."); } // 检查灯光状态(此例中未使用,仅为完整性) public boolean isOn() { return isOn; }
}
接下来,定义命令接口和它的实现类,如下代码:
// 命令接口
public interface Command { void execute(); void undo();
} // 打开灯光命令实现
public class TurnOnLightCommand implements Command { private Light light; public TurnOnLightCommand(Light light) { this.light = light; } @Override public void execute() { light.turnOn(); } @Override public void undo() { light.turnOff(); }
} // 关闭灯光命令实现
public class TurnOffLightCommand implements Command { private Light light; public TurnOffLightCommand(Light light) { this.light = light; } @Override public void execute() { light.turnOff(); } @Override public void undo() { light.turnOn(); }
}
然后,定义一个调用者类SmartHomeController
,它不直接调用接收者,而是使用命令对象来执行操作,如下代码:
// 智能家居控制器类,作为调用者
import java.util.Stack; public class SmartHomeController { private Stack<Command> commandStack = new Stack<>(); // 执行命令,并将命令压入栈中以支持撤销 public void executeCommand(Command command) { command.execute(); commandStack.push(command); } // 撤销上一个命令 public void undoLastCommand() { if (!commandStack.isEmpty()) { Command lastCommand = commandStack.pop(); lastCommand.undo(); } }
}
最后,在客户端代码中使用SmartHomeController
来控制灯光的打开和关闭,并展示撤销功能:
// 客户端代码
public class Client { public static void main(String[] args) { // 创建灯光对象 Light light = new Light(); // 创建智能家居控制器对象 SmartHomeController controller = new SmartHomeController(); // 创建并打开灯光命令 Command turnOnCommand = new TurnOnLightCommand(light); controller.executeCommand(turnOnCommand); // 创建并关闭灯光命令 Command turnOffCommand = new TurnOffLightCommand(light); controller.executeCommand(turnOffCommand); // 撤销上一个命令(关闭灯光),灯光应该会再次打开 controller.undoLastCommand(); }
}
输出结果:
Light is on.
Light is off.
Light is on.
在这个例子中,SmartHomeController
不再直接调用Light
对象的方法,而是通过Command
接口间接地执行操作,这种间接层允许在不修改调用者和接收者的情况下添加新功能,比如日志记录。此外,由于调用者和接收者之间的解耦,可以轻松地替换灯光控制的实现,比如使用远程服务器来控制灯光,而不需要修改SmartHomeController
的代码。
核心总结
命令模式,作为行为设计模式的一种,其核心思想在于将请求或操作封装成对象,这种封装不仅让请求本身变得更加具体和可管理,更重要的是,它实现了请求发送者与请求接收者之间的解耦。在传统的程序设计中,请求的发送者往往直接调用接收者的方法,这种方式虽然简单直接,但会导致发送者和接收者之间紧密耦合,不利于代码的维护和扩展。
在复杂的业务场景中,特别是当存在多个调用者和接收者,并且这些组件之间需要解耦时,命令模式就显得尤为有用。通过命令模式可以将调用者和接收者完全分离,调用者不再直接调用接收者的方法,而是通过一个中间的命令对象来间接地发出请求,这个命令对象封装了接收者的方法调用和相关的参数,它可以在调用者和接收者之间传递,并在适当的时候由调用者执行。
其它思考
命令模式在一些设计模式中可能会与其他模式产生混淆,尤其是与策略模式和状态模式,如下:
- 策略模式,策略模式定义了一系列算法,并将每个算法封装起来,使它们可以互换,策略模式关注的是算法的替换问题,允许在运行时选择使用哪个算法,而命令模式则更关注于请求的封装、排队和撤销等操作,尽管两者都涉及到了行为的封装和替换,但它们的意图和使用场景是不同的。
- 状态模式,状态模式允许对象在内部状态改变时改变其行为,看起来好像修改了其类,状态模式与命令模式在处理对象行为方面有一定的相似性,但它们解决的问题不同,状态模式关注的是对象状态变化时的行为改变,而命令模式则关注于将请求封装为对象以实现解耦和撤销等操作。
总结:策略模式关注算法的替换,状态模式关注状态变化时的行为改变,而命令模式关注请求的封装、排队和撤销等操作。
完!