应对软件需求变化-命令模式的应用

一、缘起

在软件构建过程中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合--比如需要对行为进行“记录、撤销/重做(undo/redo)、事务”等处理,这种无法抵御变化的紧耦合是不适合的

在这种情况下,如何将“行为请求者”与“行为实现者”解耦

将一组行为抽象为对象,可以实现两者之间的松耦合。

二、定义

将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

三、应用举例

定义一个遥控器作为行为请求者,遥控器有多个按钮,每个按钮对应到一个命令。当按下按钮,相应命令对象的execute()方法就会被调用,其结果就是,行为实现者(例如:电灯、天花板电扇、音响)的动作被调用。

实现遥控器:

import java.util.*;

//
// This is the invoker
//
public class RemoteControlWithUndo {
    Command[] onCommands;   //遥控器处理多个开与关的命令,使用相应数组记录这些命令。另外还要处理取消命令。
    Command[] offCommands;
    Command undoCommand;
 
    public RemoteControlWithUndo() {  //在构造器中,只需实例化并初始化这两个开关数组和取消命令。
        onCommands = new Command[7];
        offCommands = new Command[7];
 
        Command noCommand = new NoCommand();
        for(int i=0;i<7;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 String toString() {
        StringBuffer stringBuff = new StringBuffer();
        stringBuff.append("\n------ Remote Control -------\n");
        for (int i = 0; i < onCommands.length; i++) {
            stringBuff.append("[slot " + i + "] " + onCommands[i].getClass().getName()
                + "    " + offCommands[i].getClass().getName() + "\n");
        }
        stringBuff.append("[undo] " + undoCommand.getClass().getName() + "\n");
        return stringBuff.toString();
    }
}

命令接口:

public interface Command {
    public void execute();
    public void undo();
}

实现命令。开灯命令:

public class LightOnCommand implements Command {
    Light light;
    int level;
    public LightOnCommand(Light light) {
        this.light = light;
    }
 
    public void execute() {
        level = light.getLevel();
        light.on();
    }
 
    public void undo() {
        light.dim(level);
    }
}

关灯命令:

public class LightOffCommand implements Command {
    Light light;
    int level;
    public LightOffCommand(Light light) {
        this.light = light;
    }
 
    public void execute() {
        level = light.getLevel();
        light.off();
    }
 
    public void undo() {
        light.dim(level);
    }
}

开调光器命令:

public class DimmerLightOnCommand implements Command {
    Light light;
    int prevLevel;

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

    public void execute() {
        prevLevel = light.getLevel();
        light.dim(75);
    }

    public void undo() {
        light.dim(prevLevel);
    }
}

关调光器命令:

public class DimmerLightOffCommand implements Command {
    Light light;
    int prevLevel;

    public DimmerLightOffCommand(Light light) {
        this.light = light;
        prevLevel = 100;
    }

    public void execute() {
        prevLevel = light.getLevel();
        light.off();
    }

    public void undo() {
        light.dim(prevLevel);
    }
}

吊扇开小风命令:

public class CeilingFanLowCommand implements Command {
    CeilingFan ceilingFan;
    int prevSpeed;
  
    public CeilingFanLowCommand(CeilingFan ceilingFan) {
        this.ceilingFan = ceilingFan;
    }
 
    public void execute() {
        prevSpeed = ceilingFan.getSpeed();
        ceilingFan.low();
    }
 
    public void undo() {
        if (prevSpeed == CeilingFan.HIGH) {
            ceilingFan.high();
        } else if (prevSpeed == CeilingFan.MEDIUM) {
            ceilingFan.medium();
        } else if (prevSpeed == CeilingFan.LOW) {
            ceilingFan.low();
        } else if (prevSpeed == CeilingFan.OFF) {
            ceilingFan.off();
        }
    }
}

吊扇开中风命令:

public class CeilingFanMediumCommand implements Command {
    CeilingFan ceilingFan;
    int prevSpeed;
  
    public CeilingFanMediumCommand(CeilingFan ceilingFan) {
        this.ceilingFan = ceilingFan;
    }
 
    public void execute() {
        prevSpeed = ceilingFan.getSpeed();
        ceilingFan.medium();
    }
 
    public void undo() {
        if (prevSpeed == CeilingFan.HIGH) {
            ceilingFan.high();
        } else if (prevSpeed == CeilingFan.MEDIUM) {
            ceilingFan.medium();
        } else if (prevSpeed == CeilingFan.LOW) {
            ceilingFan.low();
        } else if (prevSpeed == CeilingFan.OFF) {
            ceilingFan.off();
        }
    }
}

吊扇开大风命令:

public class CeilingFanHighCommand implements Command {
    CeilingFan ceilingFan;
    int prevSpeed;
  
    public CeilingFanHighCommand(CeilingFan ceilingFan) {
        this.ceilingFan = ceilingFan;
    }
 
    public void execute() {
        prevSpeed = ceilingFan.getSpeed();
        ceilingFan.high();
    }
 
    public void undo() {
        if (prevSpeed == CeilingFan.HIGH) {
            ceilingFan.high();
        } else if (prevSpeed == CeilingFan.MEDIUM) {
            ceilingFan.medium();
        } else if (prevSpeed == CeilingFan.LOW) {
            ceilingFan.low();
        } else if (prevSpeed == CeilingFan.OFF) {
            ceilingFan.off();
        }
    }
}

关吊扇命令:

public class CeilingFanOffCommand implements Command {
    CeilingFan ceilingFan;
    int prevSpeed;
  
    public CeilingFanOffCommand(CeilingFan ceilingFan) {
        this.ceilingFan = ceilingFan;
    }
 
    public void execute() {
        prevSpeed = ceilingFan.getSpeed();
        ceilingFan.off();
    }
 
    public void undo() {
        if (prevSpeed == CeilingFan.HIGH) {
            ceilingFan.high();
        } else if (prevSpeed == CeilingFan.MEDIUM) {
            ceilingFan.medium();
        } else if (prevSpeed == CeilingFan.LOW) {
            ceilingFan.low();
        } else if (prevSpeed == CeilingFan.OFF) {
            ceilingFan.off();
        }
    }
}

默认命令:

public class NoCommand implements Command {
    public void execute() { }
    public void undo() { }
}

行为实现者。灯:

public class Light {
    String location;
    int level;

    public Light(String location) {
        this.location = location;
    }

    public void on() {
        level = 100;
        System.out.println("Light is on");
    }

    public void off() {
        level = 0;
        System.out.println("Light is off");
    }

    public void dim(int level) {
        this.level = level;
        if (level == 0) {
            off();
        }
        else {
            System.out.println("Light is dimmed to " + level + "%");
        }
    }

    public int getLevel() {
        return level;
    }
}

吊扇:

public class CeilingFan {
    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 CeilingFan(String location) {
        this.location = location;
        speed = OFF;
    }
  
    public void high() {
        speed = HIGH;
        System.out.println(location + " ceiling fan is on high");
    } 
 
    public void medium() {
        speed = MEDIUM;
        System.out.println(location + " ceiling fan is on medium");
    }
 
    public void low() {
        speed = LOW;
        System.out.println(location + " ceiling fan is on low");
    }
  
    public void off() {
        speed = OFF;
        System.out.println(location + " ceiling fan is off");
    }
  
    public int getSpeed() {
        return speed;
    }
}

遥控器客户端,测试遥控器:

public class RemoteLoader {
 
    public static void main(String[] args) {
        RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();
 
        Light livingRoomLight = new Light("Living Room");
 
        LightOnCommand livingRoomLightOn = 
                new LightOnCommand(livingRoomLight);
        LightOffCommand livingRoomLightOff = 
                new LightOffCommand(livingRoomLight);
 
        remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
 
        remoteControl.onButtonWasPushed(0);
        remoteControl.offButtonWasPushed(0);
        System.out.println(remoteControl);
        remoteControl.undoButtonWasPushed();
        remoteControl.offButtonWasPushed(0);
        remoteControl.onButtonWasPushed(0);
        System.out.println(remoteControl);
        remoteControl.undoButtonWasPushed();

        CeilingFan ceilingFan = new CeilingFan("Living Room");
   
        CeilingFanMediumCommand ceilingFanMedium = 
                new CeilingFanMediumCommand(ceilingFan);
        CeilingFanHighCommand ceilingFanHigh = 
                new CeilingFanHighCommand(ceilingFan);
        CeilingFanOffCommand ceilingFanOff = 
                new CeilingFanOffCommand(ceilingFan);
  
        remoteControl.setCommand(0, ceilingFanMedium, ceilingFanOff);
        remoteControl.setCommand(1, ceilingFanHigh, ceilingFanOff);
   
        remoteControl.onButtonWasPushed(0);
        remoteControl.offButtonWasPushed(0);
        System.out.println(remoteControl);
        remoteControl.undoButtonWasPushed();
  
        remoteControl.onButtonWasPushed(1);
        System.out.println(remoteControl);
        remoteControl.undoButtonWasPushed();
    }
}

四、总结

Command模式的根本目的在于将“行为请求者”与“行为实现者”解耦,在面向对象语言中,常见的实现手段是“将行为抽象为对象”。

在被解耦的两者之间是通过命令对象进行沟通的

命令对象封装了接收者和一个或一组动作。

调用者通过调用命令对象的execute()发出请求,这会使得接收者的动作被调用。

调用者可以接受命令当做参数,甚至在运行时动态地进行。

命令可以支持撤销,做法是实现一个undo()方法来回到execute()被执行前的状态。

posted @ 2020-06-07 20:59  windpoplar  阅读(247)  评论(0编辑  收藏  举报