封装调用:将方法调用给封装起来。

这次讲的是命令模式,他的作用就是方法给封装起来,有需要的时候就调用,调用者并不需要关心它是如何实现的。

我们来看一张流程图
注:
1、订单封装了准备餐点的请求,
2、女服务员的工作是接受订单,然后调用订单的orderUp方法,女服务员并不关心订单内容是什么,她只需调用orderUp方法
3、厨师就是一个对象,他是真正知道餐点的具体内容的人,一旦女服务员调用orderUp方法,厨师就接手,实现餐点的具体方法,这里厨师和女服务员是解耦的,订单封装了餐点的细节,她只要调用每个订单的方法即可,而厨师看了订单就知道该做些什么餐点。

图片描述

好的,我重新绘制了一张图反映命令模式如下图,流程与上图相同。
图片描述

OK,我们基本了解了命令模式的流程怎样
下面写一个模拟遥控器打开电灯和门这么个动作的Demo例子

1、写电灯和门的类

电灯

package Entity;

public class Light {
    public Light() {
    }

    public void on() {

        System.out.println("灯亮了");
    }

    public void off() {
        System.out.println("灯灭了");
    }
}

package Entity;

public class Door {
    public Door() {
    }

    public void OpenDoor() {
        System.out.println("门开了");
    }

    public void OffDoor() {
        System.out.println("门关了");
    }
    
    public void StopDoor(){
        System.out.println("门停止了");
    }
    
    public void DoorLightOn(){
        System.out.println("门里的灯亮了");
    }
}

2、创建命令接口

package Interface;
/**
 * 命令接口
 * 
 * @author Joy
 * 
 */
public interface Command {
    // 命令执行方法
    public void execute();
}

3、实现一个打开电灯的命令

package Implements;

import Entity.Light;
import Interface.Command;

public class LightOnCommand implements Command {
    Light light;

    // 构造器中传入某个电灯类型
    // 以便让这个命令控制,然后记录在light实例变量中,
    // 当调用execute时,light会根据类型不同执行不同灯亮方法
    public LightOnCommand(Light light) {
        this.light = light;
    }

    // 执行灯亮方法
    @Override
    public void execute() {
        light.on();
    }
}

门的实现方法

package Implements;

import Entity.Door;
import Interface.Command;

public class DoorOpenCommand implements Command {
    Door door;

    public DoorOpenCommand(Door door) {
        this.door = door;
    }

    @Override
    public void execute() {
        door.OpenDoor();
    }
}

4、调用命令对象(遥控器)

package Control;

import Interface.Command;

/**
 * 简单遥控器
 *相当于调用者
 * @author Joy
 * 
 */
public class SimpleRemoteControl {
    // 命令对象类型,相当于插槽控制着一个装置
    Command slot;

    public SimpleRemoteControl() {
    }

    // 这个方法用来设置插槽控制的命令
    // 如果客户需要改变遥控器按钮的行为,可以多次调用此方法
    public void setCommand(Command command) {
        slot = command;
    }

    // 执行方法
    public void buttonWasPressed() {
        slot.execute();
    }
}

5、测试类(使用遥控器)

package TestMain;

import Control.SimpleRemoteControl;
import Entity.Door;
import Entity.Light;
import Implements.DoorOpenCommand;
import Implements.LightOnCommand;

public class LightTestMain {
    public static void main(String[] args) {
        // 遥控器相当于命令的调用者,会传入一个命令对象,可以用来发送请求
        SimpleRemoteControl remote = new SimpleRemoteControl();
        // 创建一个电灯对象,此对象也就是请求中的接收者
        Light light = new Light();
        Door door = new Door();
        // 创建打开电灯动作的类,并传入接收者(light)
        LightOnCommand lightOn = new LightOnCommand(light);
        DoorOpenCommand doorOpen = new DoorOpenCommand(door);
        // 命令传给调用者(遥控器)
        remote.setCommand(lightOn);
        // 模拟按下按钮
        remote.buttonWasPressed();
        remote.setCommand(doorOpen);
        remote.buttonWasPressed();
    }
}

效果图
图片描述

这就是个基本命令模式的使用算是牛刀小试了一下,

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

下面是这模式的类图
图片描述

接下来继续模拟一个遥控器,只不过这次需求变得复杂了起来。
示例图如下,
图片描述
每个遥控器的插槽都对应一个命令吗,这样遥控器就变为了“调用者”,当按下按钮,对应的命令对象的execute方法就会被调用,结果就是接收者(例如:电灯,天花板电扇,音响)的动作被调用。

代码开始
1、实体类

电灯

package Entity;

public class Light {
    //所处位置
    String location;

    public Light(String location) {
        this.location = location;
    }
    public void on(){
        System.out.println(location+"灯亮了");
    }
    
    public void off(){
        System.out.println(location+"灯关了");
    }
}

音响

package Entity;

/**
 * 音响类
 * 
 * @author Joy
 * 
 */
public class Stereo {
    //location 一个地点变量
    String location;

    public Stereo(String location) {
        this.location = location;
    }
    
    public void on(){
        System.out.println(location+"音响启动");
    }
    
    public void off(){
        System.out.println(location+"音响关闭");
    }
    
    public void setDVD(){
        System.out.println(location+"音响放一张DVD并播放");
    }
    
    public void setCD(){
        System.out.println(location+"音响放一张CD并播放");
    }
    public void setRadio(){
        System.out.println(location+"音响以收音机无线电形式播放");
    }
    
    public void setVolume(int volume) {        
        System.out.println(location + " 音响音量设置为 " + volume);
    }        
}

电视

package Entity;

public class TV {
    String location;
    int channel;//电视频道

    public TV(String location) {
        
        this.location = location;
    }
    
    public void TVOn(){
        System.out.println(location+"电视自动打开了");
    }
    public void TVOff(){
        System.out.println(location+"电视自动关闭了");
    }
    //电视调频道
    public void setTVChannel(int channel){
        this.channel=channel;
        System.out.println(location+"电视自动调到"+channel+"频道");
    }
}

2、创建命令接口对象

package Interface;
public interface Command {
    //执行
    public void execute();
    //撤销
    public void undo();
}

3、实现遥控器类(调用者)

package Control;

import Implements.NoCommand;
import Interface.Command;

/**
 * 实现遥控器
 * 
 * @author Joy
 */
public class RemoteControl {
    // 此时遥控器要处理7个开关控制,使用数组
    Command[] onCommands = new Command[7];
    Command[] offCommands = new Command[7];
    // 撤销当然要先知道之前的命令
    // 撤销变量,用来追踪最后被调用的命令
    Command undoCommand;

    // 初始化遥控器类,一开始都是无操作noCommand是一个无操作对象
    // 在测试输出时,没有被明确指明命令的插槽,其命令默认为noCommand对象
    public RemoteControl() {
        Command noCommand = new NoCommand();
        for (int i = 0; i < onCommands.length; i++) {
            onCommands[i] = noCommand;
            offCommands[i] = noCommand;
        }
        undoCommand = noCommand;
    }

    /**
     * 
     * @param slot
     *            :插槽的位置(类似索引值)
     * @param onCommand
     *            :开的命令
     * @param offCommand
     *            :关的命令 这些命令被记录在开关数组对应的插槽位置上,以便使用
     */
    public void setCommand(int slot, Command onCommand, Command offCommand) {
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }

    /**
     * 开关按钮是对应的插槽位置负责调用对应的方法 
     * 遥控器上面开关按钮的不同位置就可以控制不同类型的灯
     * undoCommand:当按下遥控器按钮时,我们取得这个命令,并记录在undoCommand里
     * @param slot
     */
    public void onButtonWasPushed(int slot) {
        onCommands[slot].execute();
        undoCommand=onCommands[slot];
    }

    public void offButtonWasPushed(int slot) {
        offCommands[slot].execute();
        undoCommand=offCommands[slot];
    }
    //添加一个撤销按钮
    public void undoButtonWasPushed(){
        //撤销
        undoCommand.undo();
    }

    // 打印每个插槽和它对应的命令
    @Override
    public String toString() {
        StringBuffer sbf = new StringBuffer();
        sbf.append("\n======================遥控器======================\n");
        for (int i = 0; i < onCommands.length; i++) {
            sbf.append("[插槽" + i + "]" + onCommands[i].getClass().getName()
                    + "\t" + offCommands[i].getClass().getName() + "\n");
        }
    
        return sbf.toString();
    }
}

4、实现各个命令(7个)

package Implements;

import Entity.Light;
import Interface.Command;

public class LightOffCommand implements Command {
    //具体对象变量
    Light light;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.off();

    }

    @Override
    public void undo() {
        light.on();
        
    }
}
package Implements;

import Entity.Light;
import Interface.Command;

public class LightOnCommand implements Command {
    // 具体对象变量
    Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    // 执行打开电灯方法
    @Override
    public void execute() {
        light.on();

    }

    // 撤销操作,关闭电灯
    @Override
    public void undo() {
        light.off();

    }
}
package Implements;

import Interface.Command;

public class NoCommand implements Command {
    // 这是个无操作类,插槽内的类没有实例化是就走这个对象
    @Override
    public void execute() {

    }

    @Override
    public void undo() {
        // TODO 自动生成的方法存根
        
    }
}
package Implements;

import Entity.Stereo;
import Interface.Command;

public class StereoOffCommand implements Command {
    // 具体对象变量
    Stereo stereo;

    public StereoOffCommand(Stereo stereo) {
        this.stereo = stereo;
    }

    public void execute() {
        stereo.off();
    }

    @Override
    public void undo() {
        stereo.on();
    }
}
package Implements;

import Entity.Stereo;
import Interface.Command;

/**
 * 音响的
 * 
 * @author Joy
 * 
 */
public class StereoOnWithCDCommand implements Command {
    // 具体对象变量
    Stereo stereo;

    public StereoOnWithCDCommand(Stereo stereo) {
        this.stereo = stereo;
    }

    // 具体实现方法(方法再去调用实现方法)
    public void execute() {
        stereo.on();
        stereo.setCD();
        stereo.setVolume(11);
    }

    @Override
    public void undo() {
    stereo.off();
        
    }
}
package Implements;

import Entity.TV;
import Interface.Command;

public class TVOffCommand implements Command {
    TV tv;

    public TVOffCommand(TV tv) {
        this.tv = tv;
    }

    @Override
    public void execute() {
        tv.TVOff();
    }

    @Override
    public void undo() {
        tv.TVOn();
    }
}
package Implements;

import Entity.TV;
import Interface.Command;

public class TVOnCommand implements Command {
    TV tv;

    public TVOnCommand(TV tv) {
        this.tv = tv;
    }

    @Override
    public void execute() {
        tv.TVOn();
        tv.setTVChannel(15);

    }

    @Override
    public void undo() {
        tv.TVOff();
    }
}

5、测试类

package TestMain;

import Control.RemoteControl;
import Entity.Light;
import Entity.Stereo;
import Entity.TV;
import Implements.LightOffCommand;
import Implements.LightOnCommand;
import Implements.StereoOffCommand;
import Implements.StereoOnWithCDCommand;
import Implements.TVOffCommand;
import Implements.TVOnCommand;

public class TestMain {
    public static void main(String[] args) {
        // 实例化遥控器
        RemoteControl remoteControl = new RemoteControl();
        // 实例化需要控制对象,并传入房子位置
        Stereo stereo = new Stereo("客厅");
        Light light = new Light("客厅");
        TV tv = new TV("卧室");
        // 调用设备开关方法
        StereoOnWithCDCommand stereoOnWichCD = new StereoOnWithCDCommand(stereo);
        StereoOffCommand stereoOffWithCD = new StereoOffCommand(stereo);
        LightOnCommand lightOn = new LightOnCommand(light);
        LightOffCommand lightOff = new LightOffCommand(light);
        TVOnCommand tvOn = new TVOnCommand(tv);
        TVOffCommand tvOff = new TVOffCommand(tv);

        // 设置插槽位置(遥控器的哪个按钮对应哪个设备开关)
        remoteControl.setCommand(0, lightOn, lightOff);
        remoteControl.setCommand(3, stereoOnWichCD, stereoOffWithCD);
        remoteControl.setCommand(5, tvOn, tvOff);
        // 输出插槽位置
        System.out.println(remoteControl);
        // 按下开关
        remoteControl.onButtonWasPushed(0);
        remoteControl.offButtonWasPushed(0);
        remoteControl.onButtonWasPushed(3);
        remoteControl.offButtonWasPushed(3);
        remoteControl.onButtonWasPushed(5);
        remoteControl.offButtonWasPushed(5);
    }
}

效果图
图片描述

+1~~~~
在这个实例当中我特意预留了撤销的功能,让我们看看加上撤销功能(undo)的遥控器是怎么运行的把。
新建一个undoCommandTest类

package TestMain;

import Control.RemoteControl;
import Entity.Light;
import Implements.LightOffCommand;
import Implements.LightOnCommand;

public class undoCommandTest {
    public static void main(String[] args) {
        // 实例化遥控器
        RemoteControl remoteControl = new RemoteControl();
        // 实例化需要控制对象,并传入房子位置
        Light light = new Light("客厅");

        // 调用设备开关方法
        LightOnCommand lightOn = new LightOnCommand(light);
        LightOffCommand lightOff = new LightOffCommand(light);

        // 设置插槽位置(遥控器的哪个按钮对应哪个设备开关)
        remoteControl.setCommand(0, lightOn, lightOff);

        // 按下开关
        remoteControl.onButtonWasPushed(0);
        remoteControl.offButtonWasPushed(0);
        // 输出插槽位置
        System.out.println(remoteControl);
        // 撤销
        System.out.println("按下撤销按钮");
        remoteControl.undoButtonWasPushed();
        System.out.println("");
        remoteControl.offButtonWasPushed(0);
        remoteControl.onButtonWasPushed(0);
        System.out.println(remoteControl);
        System.out.println("按下撤销按钮");
        remoteControl.undoButtonWasPushed();
    }
}

效果图
图片描述
看来撤销的功能也OK,我脑中甚至浮现出在JavaWeb里这个撤销的效果了,23333。

感谢你看到这里,命令模式的上部分到这里就结束了,本人文笔随便,若有不足或错误之处望给予指点,90度弯腰~~~很快我会发布命令模式下的内容,生命不息,编程不止!

参考书籍:《Head First 设计模式》

暖心先森
287 声望16 粉丝

生命不息,编程不止!