设计模式之----命令模式
程序员文章站
2022-05-26 08:46:41
...
设计模式之命令模式
- 命令模式概念
- 命令模式实践以及优缺点
- 命令模式案例
命令模式概念
- 命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令;总之就是 : 将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。
- 将请求、命令、动作等封装成对象,这样可以让项目使用这些对象来参数化其他对象,使得命令的请求者和执行者解耦;
命令模式实践以及优缺点
- 何时使用:在某些场合,比如要对行为进行”记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将”行为请求者”与”行为实现者”解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。
- 如何解决:通过调用者调用接受者执行命令,顺序:调用者→接受者→命令。
- 优点: 1、降低了系统耦合度。 2、新的命令可以很容易添加到系统中去。
- 缺点:使用命令模式可能会导致某些系统有过多的具体命令类。
- 相关用途: 1.宏命令模式:命令模式 加 组合模式,我们可以将多个命令组合到一起来实现命令的批处理。;2.队列请求:将命令排成一个队列打包,一个个调用 execute 方法,如线程池的任务队列,线程不关心任务队列中是读 IO 还是计算,只取出命令后执行,接着进行下一个;3.日志请求:某些应用需要我们将所有的动作记录在日志中,然后在系统死机等情况出现时,重新调用这些动作恢复到之前的状态。如数据库事务。
命令模式案例
设使用遥控器(多排开关(没排控制一个家电功能),每排开关上面有on和off两个按钮,分别代表的是打开和关闭)来控制各种家电。
传统的设计方案
package command.bad;
/**
* 控制的灯
*/
public class Light {
String loc = "";//表示的是哪里的灯,比如卧室的灯,厨房的灯
public Light(String loc) {
this.loc = loc;
}
//打开某个灯
public void on(){
System.out.println(loc + " On!");
}
//关闭某个灯
public void off(){
System.out.println(loc + " Off!");
}
}
package command.bad;
/**
* 控制的音响
*/
public class Stereo {
public int volume;//这个是设置音响的音量
public Stereo(int volume) {
this.volume = volume;
}
public void on(){
System.out.println("Stereo on!");
}
public void off(){
System.out.println("stereo off!");
}
public void setCd(){ //设置歌曲
System.out.println("Stereo setCd!");
}
public void setVolume(int volume){
this.volume = volume;
System.out.println("Stereo volume = " + this.volume);
}
public int getVolume(){
return this.volume;
}
public void start(){
System.out.println("Stereo start!");
}
}
遥控器接口
package command.bad;
/**
* 这个是控制器的接口
*/
public interface Control {
//slot是槽的意思 就是一排接口有一些槽,这里编号从0开始
public void onButton(int slot);
public void offButton(int slot);
}
package command.bad;
/**
* 一个传统的遥控器的设计方案
*/
public class TraditionControl implements Control {
private Light light;
private Stereo stereo;//两个遥控器要控制的家电
public TraditionControl(Light light, Stereo stereo) {
this.light = light;
this.stereo = stereo;
}
@Override
public void onButton(int slot) {
switch (slot){
case 0: light.on();break;//0行插槽
case 1: stereo.on();break; //1行插槽 控制音响的开关
case 2: //控制音响音量
int vol = stereo.getVolume();
if(vol < 11) stereo.setVolume(++vol);
break;
}
}
@Override
public void offButton(int slot) {
switch (slot){
case 0: light.off();break;//0行插槽
case 1: stereo.off();break; //1行插槽 控制音响的开关
case 2: //控制音响音量
int vol = stereo.getVolume();
if(vol > 0) stereo.setVolume(--vol);
break;
}
}
}
测试类
package command.bad;
public class MyTest {
public static void main(String[] args) {
Light light = new Light("Bedroom");
Stereo stereo = new Stereo(0); //一开始0音量
Control control = new TraditionControl(light,stereo);
control.onButton(0); //打开第一排的on按钮
control.offButton(0);
//第二排的
control.onButton(1);
control.offButton(1);
//第三排的
control.onButton(2);
control.offButton(2);
}
}
测试效果:
使用命令模式设计方案
主要是通过命令对象(里面有excute函数和undo函数),将遥控器和具体设备解耦合
首先看command包中类,从上到下:
Command类:
package command.good.command;
/**
* 命令的接口 等下所有的Command都要实现这个接口并重写方法
*/
public interface Command {
public void excute(); //执行命令
public void undo(); //回退 遥控器按错了,回退
}
LightOffCommand类:
package command.good.command;
import command.good.furniture.Light;
/**
* 关闭灯的命令
*/
public class LightOffCommand implements Command{
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void excute() {
light.off();
}
@Override
public void undo() {
light.on(); //原来是关掉的,现在就是打开
}
}
LightOnCommand类:
package command.good.command;
import command.good.furniture.Light;
/**
* 电灯开 的接口
*/
public class LightOnCommand implements Command{
private Light light ; //哪个电灯
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void excute() {
light.on(); //直接打开
}
@Override
public void undo() {
light.off(); //原来是打开的就是关掉
}
}
MarcoCommand类(可以控制多个家具):
package command.good.command;
/**
* 宏命令 一个命令可以控制多个 家具
*/
public class MarcoCommand implements Command{
private Command[] commands;
public MarcoCommand(Command[] commands) {
this.commands = commands;
}
@Override
public void excute() {
for(int i = 0; i < commands.length; i++){
commands[i].excute();
}
}
@Override
public void undo() {
for(int i = commands.length - 1; i >= 0; i--){
commands[i].undo();
}
}
}
NoCommand类(初始化的时候方便):
package command.good.command;
/**
* 这个就是 啥也不做 很有用 初始化的时候
* 并不是每一个按钮都对应着家电,有可能是空的,这样下面就不要判断是不是空了
*/
public class NoCommand implements Command{
@Override
public void excute() {
}
@Override
public void undo() {
}
}
StereoAddVolCommand类:
package command.good.command;
import command.good.furniture.Stereo;
/**
* 加音量命令
*/
public class StereoAddVolCommand implements Command{
private Stereo stereo;
public StereoAddVolCommand(Stereo stereo) {
this.stereo = stereo;
}
@Override
public void excute() {
int vol = stereo.getVolume();
if(vol < 11) stereo.setVolume(++vol);
}
@Override
public void undo() {
int vol = stereo.getVolume();
if(vol > 0) stereo.setVolume(--vol);
}
}
StereoOffCommand类:
package command.good.command;
import command.good.furniture.Stereo;
/**
* 音响的 关闭
*/
public class StereoOffCommand implements Command{
private Stereo stereo;
public StereoOffCommand(Stereo stereo) {
this.stereo = stereo;
}
@Override
public void excute() {
stereo.off();
}
@Override
public void undo() {
stereo.on();
stereo.setCd();
}
}
StereoOnCommand类:
package command.good.command;
import command.good.furniture.Stereo;
/**
* 音响的打开和 选CD
*/
public class StereoOnCommand implements Command{
private Stereo stereo;
public StereoOnCommand(Stereo stereo) {
this.stereo = stereo;
}
@Override
public void excute() {
stereo.on();
stereo.setCd();
}
@Override
public void undo() {
stereo.off();
}
}
StereoSubVolCommand类:
package command.good.command;
import command.good.furniture.Stereo;
public class StereoSubVolCommand implements Command{
private Stereo stereo;
public StereoSubVolCommand(Stereo stereo) {
this.stereo = stereo;
}
@Override
public void excute() {
int vol = stereo.getVolume();
if(vol > 0)stereo.setVolume(--vol);
}
@Override
public void undo() {
int vol = stereo.getVolume();
if(vol < 11) stereo.setVolume(++vol);
}
}
再看control包,主要放的是遥控类的接口和遥控器,里面有三个方法:
package command.good.control;
/**
* 这个是控制器的接口
*/
public interface Control {
//slot是槽的意思 就是一排接口有一些槽,这里编号从0开始
public void onButton(int slot);
public void offButton(int slot);
public void undoButton(); //回退
}
package command.good.control;
import command.good.command.Command;
import command.good.command.NoCommand;
import java.util.Stack;
/**
* 遥控器
*/
public class ModelControl implements Control{
private Command[] onCommands; //一列的 开启按钮
private Command[] offCommands; //一列 关闭按钮
//为了回退 先进后出
private Stack<Command>stack = new Stack<Command>();
public ModelControl() {
//初始化
offCommands = new Command[10]; //10排
onCommands = new Command[10];
//下面就是NoCommand的作用
//并不是每一个按钮都对应着家电,有可能是空的,这样下面就不要判断是不是空了
Command noCommand = new NoCommand();
for(int i = 0; i < onCommands.length; i++){
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
}
//遥控器并不知道绑定的是什么家具 解耦合
//把命令对象设置到遥控器上 很重要 把命令封装成类 作为参数 命令传进来,绑定到某个插槽
public void setCommond(int slot,Command onCommand,Command offCommand){
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
@Override
public void onButton(int slot) {
onCommands[slot].excute();
stack.push( onCommands[slot]);
}
@Override
public void offButton(int slot) {
offCommands[slot].excute();
stack.push(offCommands[slot]);
}
//具体的回退 要回退的话,首先要记住按了哪些按钮, 可以使用栈的结构
@Override
public void undoButton() {
stack.pop().undo(); //回退
}
}
接下来就是两个家具,和第一个方案一样:
package command.good.furniture;
/**
* 控制的灯
*/
public class Light {
String loc = "";//表示的是哪里的灯,比如卧室的灯,厨房的灯
public Light(String loc) {
this.loc = loc;
}
//打开某个灯
public void on(){
System.out.println(loc + " On!");
}
//关闭某个灯
public void off(){
System.out.println(loc + " Off!");
}
}
package command.good.furniture;
/**
* 控制的音响
*/
public class Stereo {
public int volume;//这个是设置音响的音量
public Stereo(int volume) {
this.volume = volume;
}
public void on(){
System.out.println("Stereo On!");
}
public void off(){
System.out.println("stereo Off!");
}
public void setCd(){ //设置歌曲
System.out.println("Stereo setCd!");
}
public void setVolume(int volume){
this.volume = volume;
System.out.println("Stereo volume = " + this.volume);
}
public int getVolume(){
return this.volume;
}
public void start(){
System.out.println("Stereo start!");
}
}
然后就是测试类:
package command.good.test;
import command.good.command.*;
import command.good.control.ModelControl;
import command.good.furniture.Light;
import command.good.furniture.Stereo;
public class MyTest {
public static void main(String[] args) {
ModelControl control = new ModelControl(); //遥控器
//家具
//灯
Light bedRoomlight = new Light("BedRoom"); //卧室的灯
Light kitchenLight = new Light("Kitchen");//厨房的灯
//音响
Stereo stereo = new Stereo(0);
//控制卧室的开的和关的
LightOnCommand bedLightComOn = new LightOnCommand(bedRoomlight);
LightOffCommand bedLightComOff = new LightOffCommand(bedRoomlight);
//控制厨房的开的和关的
LightOnCommand kitchLightComOn = new LightOnCommand(kitchenLight);
LightOffCommand kitchLightComOff = new LightOffCommand(kitchenLight);
//音响的开关
StereoOnCommand stereoComOn = new StereoOnCommand(stereo);
StereoOffCommand stereoComOff = new StereoOffCommand(stereo);
//音响的音量调整
StereoAddVolCommand stereoComAdd = new StereoAddVolCommand(stereo);
StereoSubVolCommand stereoComSub = new StereoSubVolCommand(stereo);
//给遥控器设置
control.setCommond(0,bedLightComOn,bedLightComOff);
control.setCommond(1,kitchLightComOn,kitchLightComOff);
control.setCommond(2,stereoComOn,stereoComOff);
control.setCommond(3,stereoComAdd,stereoComSub);
//卧室灯开和关
control.onButton(0);
control.offButton(0);
//厨房灯开和关
control.onButton(1);
control.offButton(1);
//音响开和音量+
control.onButton(2);
control.onButton(3);
//音响关和音量-
control.offButton(3);
control.offButton(2);
System.out.println("----------test undo---------");
//test undo
control.onButton(2);
control.onButton(3);
control.undoButton();
control.undoButton();
System.out.println("----------按下一个按钮,可以控制多个设备--------");
Command[] onCommonds = {bedLightComOn,kitchLightComOn};
Command[] offComonds = {bedLightComOff,kitchLightComOff};
MarcoCommand onMarco = new MarcoCommand(onCommonds);
MarcoCommand offMarco = new MarcoCommand(offComonds);
//放在4号插头
control.setCommond(4,onMarco,offMarco);
control.onButton(4); //两个灯一起开
control.offButton(4); //两个灯一起关
control.undoButton(); //两个灯的操作都要回退
}
}
测试结果: