首先看看命令模式的定义:命令模式将请求封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持撤销的操作。
所谓参数化,我的理解是实际执行的对象,比如light(电灯)、strereo(音响),他们存在执行动作的方法,并且作为命令对象的成员变量
队列、日志这两样是命令模式的应用直接扩展
为什么需要命令模式:
在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活。
命令模式可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。这就是命令模式的模式动机。
假如有这样一种情景,我现在需要打开风扇,自然我需要一个遥控器,很明显这里的关系是:我——遥控器—>命令——风扇,而遥控器发出命令去控制风扇,其中,遥控器是请求者,风扇是执行者,遥控器不知道怎么让风扇转起来,他只知道发出命令,即上面的动机。
也可以这样子想象:我在餐厅里点菜,服务员他帮我转达请求,厨师帮我炒菜,而服务员是不知道怎么去炒,他仅仅转达请求而已——完全解耦了
先看看类图:
其中,我们可以把invoker看作遥控器,receiver看作动作的执行对象,即light之类,我们现在直接看一个支持撤销命令的代码并不是很难,所以直接上这个,来自《Head First 设计模式》:
这里给出一个接口和两个代表性的实体类(其实实体类有light、fan、cellingfan(这个是实现除开关两种可能性以外的多种可能性的撤销方法)),类里面有并没有罗列出来的类,但其实是差不多的,应该不会造成阅读障碍
先看实体类:他们各自有自己的行为方法,开关、换高低速(更好地表现撤销功能)
public class Light {
public void On(){
System.out.println("Light-----On");
}
public void Off(){
System.out.println("Light-----Off");
}
}
public class Fan {
public void On(){
System.out.println("Fan-----On");
}
public void Off(){
System.out.println("Fan-----Off");
}
}
public class CellingFan{
public static final int High = 3;
public static final int MEDIUM = 2;
public static final int LOW = 1;
public static final int OFF = 0;
String location;
int speed;
public CellingFan(String location) {
this.location = location;
speed = OFF;
}
public void high(){
System.out.println("Speed-----High");
speed = High;
}
public void medium(){
System.out.println("Speed-----Medium");
speed = MEDIUM;
}
public void Low(){
System.out.println("Speed-----Low");
speed = LOW;
}
public void Off(){
System.out.println("Speed-----Off");
speed = OFF;
}
public int getSpeed() {
return speed;
}
}实体类并没有什么特别指出,看上去好轻松,接下来是封装好的命令抽象,这里的命令抽象也很简单,只支持两种情况:开、关,开了撤销就是关,关了撤销就是开
public interface Command {
public void execute();
public void undo();
}
public class LightOnCommand implements Command {
Light light;
public void setLight(Light light) {this.light = light;}
@Override
public void execute() {light.On();}
@Override
public void undo() {light.Off();}
}
public class LightOffCommand implements Command {
Light light;
public void setLight(Light light) {this.light = light;}
@Override
public void execute() {light.Off();}
@Override
public void undo() {light.On();}
}
//这里就不列举fan的命令对象了这里的另外一个命令是支持多种情况的:这里只列举一个高速的命令,其他大同小异
public class CellingFanHighCommand implements Command {
CellingFan cellingFan;
int prevSpeed;
public void setCellingFan(CellingFan cellingFan) {
this.cellingFan = cellingFan;
}
@Override
public void execute() {
prevSpeed = cellingFan.getSpeed();
cellingFan.high();
}
@Override
public void undo() {
switch(prevSpeed){
case CellingFan.High:cellingFan.high();break;
case CellingFan.LOW:cellingFan.Low();break;
case CellingFan.MEDIUM:cellingFan.medium();break;
case CellingFan.OFF:cellingFan.Off();break;
default:break;
}
}
}可以看到这里有一个判断:之前的速度是什么,便让执行者执行之前的命令,我们也提到过,执行者就是那堆实体类,他们知道如何执行任务
我们还需要一个空对象来填充那些null的位置
public class noCommand implements Command {
@Override
public void execute() {}
@Override
public void undo() {}
}
到了这里,我们定义了实体类,把命令抽象了出来,还需要一个遥控器
我们的遥控器根据不同的设备设置开关按钮,还有一个撤销按钮,当按下开关、转速按钮的同时把执行这一动作的命令保存到撤销命令里面,这样想撤销的时候就可以调用undoButtonPushed()方法来执行上一次的命令的undo方法,不过要用之前得设置按钮
public class RemoteControl {
Command[] onCommands;
Command[] offCommands;
Command undoCommand;
//开关灯
//开关风扇
//转速高速、关
//转速中速、关
//转速低速、关 共10个按钮
public RemoteControl(){
onCommands = new Command[5];//负责开的
offCommands = new Command[5];//负责关的
//引入空对象这一设计理念,关于空对象会在java中的类型转换博客中提及
Command noCommand = new noCommand();
for(int i = 0;i<5;i++){
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
undoCommand = noCommand;
}
public void setCommand(int slot,Command onCommand,Command OffCommand){
onCommands[slot] = onCommand;
offCommands[slot] = OffCommand;
}
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();
}
}
接下来是客户设置遥控器(其实设置的动作可以给设计框架的人来做,调用者直接调用命令就好了)
public class Remoteoader {
public static void main(String[] args) {
RemoteControl control = new RemoteControl();
//为遥控器设定命令
//构造一个打开电灯的命令
Light light = new Light();
LightOnCommand lightOnCommand = new LightOnCommand();
LightOffCommand lightOffCommand = new LightOffCommand();
lightOnCommand.setLight(light);
lightOffCommand.setLight(light);
control.setCommand(0, lightOnCommand, lightOffCommand);
Fan fan = new Fan();
FanOnCommand fanOnCommand = new FanOnCommand();
FanOffCommand fanOffCommand = new FanOffCommand();
fanOnCommand.setFan(fan);
fanOffCommand.setFan(fan);
control.setCommand(1, fanOnCommand, fanOffCommand);
CellingFan cfan = new CellingFan("livingRoom");
CellingFanHighCommand cfhCommand = new CellingFanHighCommand();
CellingFanLowCommand cflCommand = new CellingFanLowCommand();
CellingFanMediumCommand cfmCommand = new CellingFanMediumCommand();
CellingFanOffCommand cfoCommand = new CellingFanOffCommand();
cfhCommand.setCellingFan(cfan);
cflCommand.setCellingFan(cfan);
cfmCommand.setCellingFan(cfan);
cfoCommand.setCellingFan(cfan);
control.setCommand(2, cfhCommand, cfoCommand);
control.setCommand(3, cflCommand, cfoCommand);
control.setCommand(4, cfmCommand, cfoCommand);
//至此遥控器命令已经设置完毕
System.out.println("我要开电灯!");
control.onButtonWasPushed(0);
System.out.println("不行,我要撤销啊,好浪费电");
control.undoButtonWasPushed();
System.out.println("就算浪费电,我也要开风扇");
control.onButtonWasPushed(3);
System.out.println("换中速,热的受不了");
control.onButtonWasPushed(4);
System.out.println("高速!");
control.onButtonWasPushed(2);
System.out.println("撤销吧,钱包没钱了");
control.undoButtonWasPushed();
//这里在撤销一次是不会返回到更前面的,因为这个简单的demo并没有考虑太多情况
//可以尝试用一个栈去保存撤销的命令
//control.undoButtonWasPushed();
//同理,并没有在off按钮上添加条件判断是否为开的状态,如果代码能够受控制并不会有多大问题
//但是如果代码不在控制范围内,最好加上是否为开状态的判断
System.out.println("宿舍热得要死,我现在出去买空调!关风扇!");
control.offButtonWasPushed(4);
}
}
//输出
//我要开电灯!
//Light-----On
//不行,我要撤销啊,好浪费电
//Light-----Off
//就算浪费电,我也要开风扇
//Speed-----Low
//换中速,热的受不了
//Speed-----Medium
//高速!
//Speed-----High
//撤销吧,钱包没钱了
//Speed-----Medium
//宿舍热得要死,我现在出去买空调!关风扇!
//Speed-----Off
如代码注释所述,代码不支持连续撤销,也并没有在关闭命令之前检查是否曾经开过设备,可以尝试使用一个栈去跟踪保存命令,同时也可以加入一些逻辑判断去判断设备开关状态,当然如果代码是受控制的状态,尽管不太严格但还是可以去接受的宏命令:如果你需要按下一个按钮所有设备都动起来,不妨设置一个宏命令,他跟上面的命令的不同之处就在于,上面的命令的成员变量是实体类(Light、fan),而这里他把需要的命令组成一个Command数组,他也实现了Command接口,所以支持把他设置进去遥控器按钮的这个功能,execute和undo方法是遍历这些命令,并执行:
public class MacroCommand implements Command{
Command[] commands;
public MacroCommand(Command[] commands) {this.commands = commands;}
@Override
public void execute() {
for(int i=0;i<commands.length;i++){commands[i].execute();}
}
//当然这里如果像cellingFan一样具有多种情况,undo就可能需要栈了
@Override
public void undo() {
for(int i=0;i<commands.length;i++){commands[i].undo();}
}
}
笔者偷个懒= =栈的方法就不去尝试了
看看他的优点:
1、降低系统的耦合度。
2、新的命令可以很容易地加入到系统中。
3、可以比较容易地设计一个命令队列和宏命令(组合命令)。
4、可以方便地实现对请求的Undo和Redo。
缺点:
使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。
这个缺点我们可以从内部类的方面上去改善,虽然java会对内部类也产生.class文件,可读性还是可以改善一下的,例子会在下面给出
适用环境:
1、系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
2、系统需要在不同的时间指定请求、将请求排队和执行请求。
3、系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
4、系统需要将一组操作组合在一起,即支持宏命令
我们来看看使用内部类的命令模式:例子来自java编程思想,这个我们试试从命令模式的角度思考:
public abstract class Event {
private long eventTime;
protected final long delayTime;
public Event(long delayTime){
this.delayTime = delayTime;
start();
}
//获取当前时间,并加上一个延迟时间,生成触发器的时间
//可以通过调用start重新启动计时器,System.nanoTime()是当前的精确时间
//如果需要重复一个事件,只需简单地在action中调用一下start方法,不过前提是在工作的Controller类的list中存在这个事件
public void start(){
eventTime = System.nanoTime()+delayTime;
}
//当当前时间到达要执行的时间
public boolean ready(){
return System.nanoTime()>=eventTime;
}
public abstract void action();
}
public class Controller {
private List<Event> eventList = new ArrayList<Event>();
public void addEvent(Event c){eventList.add(c);}
//遍历eventList,寻找就绪的ready、要运行的event对象
public void run(){
while(eventList.size()>0){
//把eventList复制一份是为了你在访问eventlList的同时修改他
//有同步的感觉
for(Event e:new ArrayList<Event>(eventList)){
if(e.ready()){
System.out.println(e);
e.action();
eventList.remove(e);
}
}
}
}
}到了这里控制框架基本完成了,现在我们就用内部类去为他提供具体的实现,他能在单一的类里面产生对同一个基类Event的多种导出版本public class GreenhouseControls extends Controller {
//灯光
private boolean light = false;
public class LightOn extends Event{
public LightOn(long delayTime) {super(delayTime);}
@Override
public void action() {light = true;}
public String toString(){return "Light is on";}
}
public class LightOff extends Event{
public LightOff(long delayTime) {super(delayTime);}
@Override
public void action() {light = true;}
public String toString(){return "Light is off";}
}
//水分
private boolean water = false;
public class WaterOn extends Event{
public WaterOn(long delayTime) {super(delayTime);}
@Override
public void action() {water = true;}
public String toString(){return "Water is on";}
}
public class WaterOff extends Event{
public WaterOff(long delayTime) {super(delayTime);}
@Override
public void action() {water = false;}
public String toString(){return "Water is off";}
}
//温度(白天、黑夜温度)
private String thermostat = "Day";
public class ThermostatNight extends Event{
public ThermostatNight(long delayTime) {super(delayTime);}
@Override
public void action() {thermostat = "Night";}
public String toString(){return "thermostat is Night";}
}
public class ThermostatDay extends Event{
public ThermostatDay(long delayTime) {super(delayTime);}
@Override
public void action() {thermostat = "Day";}
public String toString(){return "thermostat is Day";}
}
//响铃
public class Bell extends Event{
public Bell(long delayTime) {super(delayTime);}
@Override
public void action() {
//在这里不停的addEvent,这个方法是在Controller的run方法里面被调用
//就是说,执行一次这个方法,就往eventList里面加入一个新的Bell事件
//注意到run方法的循环条件是当eventList长度不为0的时候一直访问,可以看出这里是无限响铃的
addEvent(new Bell(delayTime));
}
public String toString(){return "Bing";}
}
//重启系统
public class Restart extends Event{
private Event[] eventList;
public Restart(long delayTime,Event[] eventList) {
super(delayTime);
this.eventList = eventList;
for(Event e:eventList){
addEvent(e);
}
}
//跟Bell类一样,重启系统会重复把你指定的list加入到eventList里面起,循环访问
//同时也将本事件加入到里面
@Override
public void action() {
for(Event e:eventList){
//start是为了让事件有一个新的延迟触发时间
e.start();
addEvent(e);
}
start();
addEvent(this);
}
public String toString(){return "Restart System";}
}
//系统的终止方法
public static class Teeminate extends Event{
public Teeminate(long delayTime) {super(delayTime);}
public void action(){System.exit(0);}
public String toString(){return "Terminating";}
}
}我们这main方法:public class GreenhouseController {
public static void main(String[] args) {
GreenhouseControls gc = new GreenhouseControls();
gc.addEvent(gc.new Bell(900));
Event[] eventList = {
gc.new ThermostatNight(0),
gc.new LightOn(200),
gc.new LightOff(400),
gc.new WaterOn(600),
gc.new WaterOff(600),
gc.new ThermostatDay(1400),
};
gc.addEvent(gc.new Restart(2000, eventList));
//我们在前面提到过,gc的run是不断地访问的,因为存在restart和bell,所以如果不想循环了,可以提供一个参数执行终止方法
if(args.length==1){
gc.addEvent(new GreenhouseControls.Teeminate(new Integer(args[0])));
}
gc.run();
}
}在这里,我们把实体类、命令类(其实在这里的命令就是事件Event)他们都放在一个GreenhouseControls里面统一管理,而每次需要用到他们时,就gc.new xxxx()这样子的方式去获取实例。这里的遥控器类是Controller,他提供的run方法我们可以想象为一个按钮,当按钮执行,真正去执行的执行者是那些命令指控的实体类(e.action),尽管这个例子有点奇怪,但是在代码的构建上清晰了很多,把实现命令的细节用一个类封装了起来
应用一:队列请求:http://sishuok.com/forum/blogPost/list/100.html(在网上找了很久没有找到关于队列请求的学习素材,谢谢这篇文章,这里仅作一下笔记)
模拟这样一种场景,餐馆中,有多个服务员为多个顾客点菜,其中处理这些菜单的多个厨师
虽然这里有很多个“多个”,先不要方,队列为我们提供了很大帮助。首先,我们理清一下思路:
1、谁是请求者——服务员Waiter类
2、命令是什么——我们这里准备两个命令:青龙过江Ccommand1类,葱爆大肠Command2类,他们共同实现Command抽象,场景是需要宏命令MenuCommand的,因为服务员是将一组菜单下单,而不是一个一个命令下单
3、谁是执行者——在上一个遥控器的例子中,命令需要一个成员变量,即实体类,他们知道如何去执行,命令让实体类去真正执行,在这里,实体类应该是厨师HotCook类。4、Command应该怎么写——我们可以提供一个接口,CookApi来描述厨师的烹饪方法,上面遥控器的例子并没有为实体类提供一个统一的接口,因为他们的行为并不一定一样,现在这里厨师的行为是一样的,是cook(),那么,既然统一了,很明显,Command抽象里面就可以提供一个setCookApi的方法,为每一道命令设置一个厨师,当然命令还需要提供设置点菜的桌号来区别那一桌
5、队列CommandQueue在哪——用list来指定队列,注意他是同步并且是单例的,因为厨师在做菜的时候,他在干掉一桌菜的时间中刚好又来了一组菜单,这样子就冲突了
接下来看看代码:先看看命令类:(实体类在更下面)
public interface Command {
//执行命令对应操作
public void execute();
//设置命令的接收者,这里是厨师,即实体类
public void setCooApi(CookApi cookapi);
//发起请求的桌号,即点菜的桌号
public int getTableNum();
}
public class Command1 implements Command {
//真正执行的人
private CookApi cookApi;
//桌号
private int tableNum;
public Command1(int tableNum) {
this.tableNum = tableNum;
}
@Override
public void execute() {
// TODO Auto-generated method stub
this.cookApi.cook(tableNum, "青龙过江");
}
@Override
public void setCooApi(CookApi cookapi) {
// TODO Auto-generated method stub
this.cookApi = cookapi;
}
@Override
public int getTableNum() {
// TODO Auto-generated method stub
return this.tableNum;
}
}
public class Command2 implements Command {
private CookApi cookApi;
private int tableNum;
public Command2(int tableNum) {
this.tableNum = tableNum;
}
@Override
public void execute() {
// TODO Auto-generated method stub
this.cookApi.cook(tableNum, "葱爆大肠");
}
@Override
public void setCooApi(CookApi cookapi) {
// TODO Auto-generated method stub
this.cookApi = cookapi;
}
@Override
public int getTableNum() {
// TODO Auto-generated method stub
return this.tableNum;
}
}单个命令是这样子的,他记录桌号、记录厨师,桌号在下命令的时候设置上去就好,并没有什么特别之处,我们使用了宏命令,看上去是这样子的:
public class MenuCommand implements Command {
//记录组合本菜单的多道菜,相当于宏命令
private Collection<Command> col = new ArrayList<Command>();
//点菜,把菜品加入到菜单
public void addCommand(Command cmd){
col.add(cmd);
}
//这里服务员完成了点单动作,让菜单的菜进队列
@Override
public void execute() {
CommandQueue.addMenu(this);
}
@Override
public void setCooApi(CookApi cookapi) { }
@Override
public int getTableNum() {return 0;}
public Collection<Command> getCommands() {return col;}
}队列作为连接服务员跟厨师的类,看上去是这样子的:其实宏命令输入到队列,服务员就完成了他的工作,已经没他的事了!然后就是厨师读取单例队列在while循环里面做菜
public class CommandQueue {
//用来存储命令对象的队列
public static List<Command> cmds = new ArrayList<Command>();
//服务员传过来一个新的菜单,需要同步,
//因为同时会有很多的服务员传入菜单,而同时又有很多厨师在从队列里取值
public synchronized static void addMenu(MenuCommand menu){
for(Command cmd:menu.getCommands()){
cmds.add(cmd);
}
}
public synchronized static Command getOneCommand(){
Command cmd = null;
if(cmds.size()>0){
cmd = cmds.get(0);
cmds.remove(0);
}
return cmd;
}
}我们还缺个服务员:他把菜单扔到队列里面去
public class Waiter {
private MenuCommand menuCommand = new MenuCommand();
public void orderDish(Command cmd){
//添加到菜单中
menuCommand.addCommand(cmd);
}
public void orderOver(){
this.menuCommand.execute();
}
}为了整理一下顺序流程,厨师我们后面看,现在我们知道关于厨师的一切只是在命令里面的cookapi,他提供cook的方法,我们现在开始点单,先看看main方法:
public class Client {
public static void main(String[] args) {
CookManager.runCookManager();
//为了简单,直接用循环模拟多个桌号点菜
//多个服务员给多号桌点菜
for(int i = 0;i<5;i++){
//创建服务员
Waiter waiter = new Waiter();
//创建命令对象,就是要点的菜
Command a = new Command1(i);
Command b = new Command2(i);
//点菜,就是把这些菜让服务员记录下来
waiter.orderDish(a);
waiter.orderDish(b);
//点菜完毕
waiter.orderOver();
}
}
}先看for循环,第一行是厨师的管理器,先撇开,看for循环,他定了5张桌子号,同时为每个桌子号提供了5个服务员,每张桌子点了两个命令,当然这里是为了方便才这样子干,你也可以自己定不同的桌子号和服务员,组合不同的命令
当服务员点单完成的时候就是队列进队完毕的时候,这时候服务员已经没事了,我们之前说过,订单完成了,厨师就会在无限循环里面取命令同时煮菜,这个就是CookManager的作用,他启动了厨师线程,好了,流程是这样子,看看执行者怎么执行
public interface CookApi {
//每个厨师都有一个cook方法
public void cook(int tableNum,String name);
}
//为什么要实现runable接口:我们这样想,每一个厨师都是一个线程
//run方法就是他们在菜单取出一道菜来煮的方法
public class HotCook implements CookApi, Runnable {
//厨师的名字,分辨这些菜谁做的
private String name;
public HotCook(String name) {
super();
this.name = name;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
//厨师一直等待有没有菜,有就煮,有就煮,没有就无限等待
//获取一道菜的命令
Command cmd = CommandQueue.getOneCommand();
if(cmd!=null){
//开始煮菜,首先为这个命令标识上是谁做的
cmd.setCooApi(this);
//执行这道命令,很明显,这道命令执行是调用了set进去的厨师的cook方法
cmd.execute();
}
}
}
@Override
public void cook(int tableNum, String name) {
int cookTime = (int) (20*Math.random());
System.out.println(this.name+"厨师正在为"+tableNum+"号桌子做:"+name);
try {
Thread.sleep(cookTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.name+"厨师为"+tableNum+"号桌子做好了"+name+"共耗时="+cookTime+"秒");
}
}
//当然是用来管理所有厨师的
public class CookManager {
private static boolean runFlag = false;
public static void runCookManager(){
if(!runFlag){
runFlag = true;
//创建三位厨师
HotCook cook1 = new HotCook("张三");
HotCook cook2 = new HotCook("李四");
HotCook cook3 = new HotCook("王五");
//启动他们的线程
Thread t1 = new Thread(cook1);
t1.start();
Thread t2 = new Thread(cook2);
t2.start();
Thread t3 = new Thread(cook3);
t3.start();
}
}
}至此,队列请求的例子完毕
head first 设计模式里面是这样子说队列请求的:想象一个工作队列,你在某一端添加命令,然后另一端是线程,线程执行下面的动作:从队列中取出一个命令,调用他的execute方法,等这个调用完成,将命令丢弃,再取出下一个命令
应用二:日志请求
某些应用需要我们将所有的动作都记录在日志中,并能在系统死机之后重新调用这些动作恢复到之前的状态,这就是动机,网上找到一篇代码,谢谢大神对我这种初学者的分享,仅学习笔记之用:http://blog.youkuaiyun.com/lovelion/article/details/8806643
其中,我们可以用一个list来干这个事,我们的日志文件只保存命令文本,记录一步步的操作,除了增加他的记录,绝不会减少记录,即你在程序里面执行了删除的命令,他记录的是删除命令的本身,而不是让日志文件返回上一条记录,而是增加一条删除记录操作(不知道我能表达清楚了没。。)
这里的存储是把所有操作完后保存到list,再保存list,然而我有一个想法是可以执行一个命令,再追加保存这个命令,尽量做到实时保存,具体的方法就不去研究了,如果有更好想法请不吝赐教
我对这些代码做了小部分修改,使用内部类的方式去实现:
这是工具类
public class FileUtil {
//将命令集合写入日志文件
public static void writeCommands(ArrayList commands) {
try {
FileOutputStream file = new FileOutputStream("config.log");
//创建对象输出流用于将对象写入到文件中
ObjectOutputStream objout = new ObjectOutputStream(new BufferedOutputStream(file));
//将对象写入文件
objout.writeObject(commands);
objout.close();
}catch(Exception e) {e.printStackTrace();}
}
//从日志文件中提取命令集合
public static ArrayList readCommands() {
ArrayList<Command> commands = new ArrayList<Command>();
try {
FileInputStream file = new FileInputStream("config.log");
//创建对象输入流用于从文件中读取对象
ObjectInputStream objin = new ObjectInputStream(new BufferedInputStream(file));
//将文件中的对象读出并转换为ArrayList类型
commands = (ArrayList)objin.readObject();
objin.close();
}catch(Exception e){e.printStackTrace();}
return commands;
}
}接着,我们需要真正干活的执行者——实体类:
//这算是实体类,真正的执行者
public class ConfigOperator implements Serializable{
public void insert(String args) {System.out.println("增加新节点:" + args);}
public void modify(String args) {System.out.println("修改节点:" + args);}
public void delete(String args) {System.out.println("删除节点:" + args);}
}我们的命令:
public abstract class Command implements Serializable{
protected String name; //命令名称
protected String args; //命令参数
public Command(String name) {this.name = name;}
public String getName() {return this.name;}
public void setName(String name) {this.name = name;}
//声明两个抽象的执行方法execute()
//Command并不一定是interface,正如上面使用内部类的例子一样,可以是提供一个模版方法让子类实现它
public abstract void execute(String args);
public abstract void execute();
}
public class Controller implements Serializable{
private ConfigOperator operator = new ConfigOperator();
//增加命令类:具体命令
class InsertCommand extends Command implements Serializable{
public InsertCommand(String name) {super(name);}
public void execute(String args) {
this.args = args;
operator.insert(args);
}
public void execute(){
operator.insert(this.args);
}
}
//修改命令类:具体命令
class ModifyCommand extends Command implements Serializable{
public ModifyCommand(String name) {super(name);}
public void execute(String args) {
this.args = args;
operator.modify(args);
}
public void execute() {
operator.modify(this.args);
}
}
class DeleteCommand extends Command implements Serializable{
public DeleteCommand(String name) {super(name);}
public void execute(String args) {
this.args = args;
operator.delete(args);
}
public void execute() {
operator.delete(this.args);
}
}
}注意内部类是怎么new出来的
接着遥控器:
public class ConfigSettingWindow {
//定义一个集合来存储每一次操作时的命令对象
private ArrayList<Command> commands = new ArrayList<Command>();
private Command command;
//注入具体命令对象
public void setCommand(Command command) {this.command = command;}
//执行配置文件修改命令,同时将命令对象添加到命令集合中
public void call(String args) {
command.execute(args);
commands.add(command);
}
//记录请求日志,生成日志文件,将命令集合写入日志文件
public void save() {
FileUtil.writeCommands(commands);
}
//从日志文件中提取命令集合,并循环调用每一个命令对象的execute()方法来实现配置文件的重新设置
public void recover() {
ArrayList list;
list = FileUtil.readCommands();
for (Object obj : list) {
((Command)obj).execute();
}
}
}客户调用:public class Client {
public static void main(String[] args) {
ConfigSettingWindow csw = new ConfigSettingWindow(); //定义请求发送者
Command command; //定义命令对象
Controller controller = new Controller();
//四次对配置文件的更改
command = controller.new InsertCommand("add");
csw.setCommand(command);
csw.call("test01");
command = controller.new ModifyCommand("modify");
csw.setCommand(command);
csw.call("test02");
System.out.println("保存配置");
csw.save();
System.out.println("恢复配置");
csw.recover();
}
}整篇代码下来不难,其实我们读到概念是不懂得,但是在例子面前也不过是这么一回事,多用还可以发掘出其他的扩展
关于命令模式的最后一点思考:最近在学校学习关于安卓的课程,当安卓请求后台的数据的时候,我们能不能也使用命令模式呢?比如手机是一个遥控器,当我按下一个button的时候相当于发出一个请求,而真正地执行者并不在安卓端,我们只需要构造一个遥控器的类、构造命令类去请求数据,在命令类里提供一个execute方法,通过线程并返回json数据,再在前台解析。当然,是不是有这样的一种设计,让我们我们开一条无线循环的线程,在安卓后台等待请求进队,进队后执行交互,得到返回数据?
命令模式将请求封装成对象,用于减少请求发送者与接收者的耦合。它支持请求的队列处理、日志记录和撤销操作。在系统中,请求者(如遥控器或服务员)通过命令对象(如灯、风扇或菜品命令)调用执行者(如电灯、风扇或厨师)的方法。命令模式降低了系统复杂度,易于扩展,但也可能导致大量具体命令类的产生。适用于需要解耦调用者和接收者、处理请求队列或实现撤销/重做功能的场景。
2910

被折叠的 条评论
为什么被折叠?



