前言:封装调用,实现调用者和执行者的解耦。
需求分析:
REQ1:
Vander的公司红红火火,接到的项目越来越多,还记得MS公司气象站项目吗, 没错这次又是他们找到了Vander,来请Vander设计一个创新控制器,这个控制器,或者可以说是遥控器,遥控器上有7个插槽(每个插槽对应一个家电,并且遥控器需要一个全局的撤销按钮),很明显,这次他们想做智能家居,他们同时送来了一个光盘,光盘里面各个类是各个家电的控制代码(如风扇/冰箱/热水器/空调等等)。他们希望Vander实现一组控制遥控器的API,让每个插槽都能够控制一个或者一组装置。下面是厂商给的类:
实际上有许许多多的家用电器,都需要用遥控器控制,远不止以上这几个类。
遥控器具体是要接哪种类型的电器都不确定,通过无线模块来完成,所以遥控器得具有一定的可扩展性并且能适配所有的电器。
分析:可能最直观的想法是直接使用一大堆if语句来完成,但是想想要先判断每个插槽是插入了哪个电器,然后再调用相应的电器进行相应的操作,假设现在的电器有几十种,这样的话维护代码具有很大难度,而且要经常改动。也不满足对修改关闭,对扩展开放的原则。下面让我们穿梭到餐厅,现在你到了香格里拉大酒店,你开始点餐,你点了法国鹅肝、美式牛扒还有一瓶82年的拉菲。你跟waiter说下单,然后waiter拿着你的菜单给厨师,厨师给你做菜。下面我们来分析这个过程:
下面通过代码来实现这个部分,你会发现你将订单的执行者(厨师)和订单的创建者(顾客)解耦了。
订单:
public class SteakOrder implements Order {
private Cooker cooker;
public SteakOrder() {
cooker = new Cooker();
}
public void orderUp() {
cooker.cookSteak();
}
}
服务员:
public class Waiter {
private Order order;
public void takeOrder(Order order) {
this.order = order;
}
public void orderUp() {
if(this.order != null) {
order.orderUp();
}
}
}
厨师:
public class Cooker {
public void cookSteak() {
System.out.println("cooking delicious steak!");
}
}
下面我们回到遥控器的设计上,遥控器实际上也是类似的,这些每个电器的开关都不一样,但是我们通过封装可以把它们统一起来,相当于打开电视、打开灯、打开冰箱、开热水器等这些动作就相当于厨师做各种各样的菜,遥控器这时候就充当了waiter的角色,遥控器并不需要知道有什么样的电器,它只需要知道这些连接它的电器都有excute方法就可以了,它只负责调用excute方法,然后由具体的电器来完成具体的命令。可能说得稍微有点抽象,来个图分析一下。
命令模式:将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
我们知道一个命令对象通过在特定的接收者上绑定一组动作来封装一个请求。要达到这一点,命令对象将动作和接收者包进对象里面,这个对象只暴露了一个excute方法,当此方法被调用的时候,接收者就会进行这些动作。从外面来看,其他对象不知道究竟哪个接收者进行这些动作,只知道如果调用excute方法,请求的目的就能达到。
下面开始设计遥控器:
从上面类图可以看出,遥控器并不需要知道具体是哪个电器进行了什么样的操作,它只管pushButton的时候调用excute方法来实现具体的功能。
命令对象:
public class AirConditionOnCommand implements Command {
private AirCondition airCondition;
public AirConditionOnCommand(AirCondition airCondition) {
this.airCondition = airCondition;
}
public void excute() {
airCondition.setCool();
airCondition.setTemperature(25);
airCondition.on();
}
}
遥控器:
public class RemoteControl {
private Command onButton[];
private Command offButton[];
public RemoteControl() {
onButton = new Command[7];
offButton = new Command[7];
for(int i=0; i<onButton.length; i++) {
onButton[i] = new NoCommand();
offButton[i] = new NoCommand();
}
}
public void setCommand(int slot, Command onCommand, Command offCommand) {
onButton[slot] = onCommand;
offButton[slot] = offCommand;
}
public void pushOnButton(int slot) {
onButton[slot].excute();
}
public void pushOffButton(int slot) {
offButton[slot].excute();
}
}
电器:
public class AirCondition {
private int temperture;
private boolean cool;
public void on() {
System.out.println("Aircondition is on with state is " + (cool? "cool":"warm")
+ " and the temperature is " + temperture + "℃ !");
}
public void setCool() {
this.cool = true;
}
public void setWarm() {
this.cool =false;
}
public void setTemperature(int temp) {
this.temperture = temp;
}
public void off() {
System.out.println("Airconditon is off!");
}
}
首先上面用了一个NoCommand,遥控器不可能一出厂就设置了有意义的命令对象,所以提供了NoCommand对象作为代用品,当调用它的excute方法时,这种对象什么事情都不做。当你不想返回一个有意义的对象时,空对象就很有用了。你也可以将处理null的责任转移给空对象。有时候空对象的使用也会当成一种设计模式。
下面我们进一步设计遥控器,首先遥控器上有多个按钮,上述的写法完成了开和关两个按钮,同样的,其余的按钮也可以这样依样画葫芦完成,但是每次要按那么多个按钮对应开启相应的电器还是麻烦了,能不能按一个按钮然后打开空调、灯、音响、热水器等呢,当然可以只需要建立一个CombineCommand对象即可,甚至你还可以建立一个UndoCommand命令来回退到刚刚的按钮状态。
家电:
public class AirCondition {
private int temperture;
private int preTemperature;
private String state;
private String preState;
public void on() {
System.out.println("Aircondition is on with state is " + state
+ " and the temperature is " + temperture + "℃ !");
}
public void setState(String state) {
preState = this.state;
this.state = state;
}
public String getState() {
return state;
}
public int getTemperture() {
return temperture;
}
public void setTemperature(int temp) {
preTemperature = this.temperture;
this.temperture = temp;
}
public void off() {
System.out.println("Airconditon is off!");
}
public void undo() {
this.state = preState;
this.temperture = preTemperature;
System.out.println("Aircondition is on with state is " + state
+ " and the temperature is " + temperture + "℃ !");
}
}
命令:
public class AirConditionOnCommand implements Command {
private AirCondition airCondition;
public AirConditionOnCommand(AirCondition airCondition) {
this.airCondition = airCondition;
}
public void excute() {
airCondition.setTemperature(airCondition.getTemperture());
airCondition.setState(airCondition.getState());
airCondition.on();
}
public void undo() {
airCondition.off();
}
}
组合命令:
public class CombineCommand implements Command {
private Command[] commands;
public CombineCommand(Command[] commands){
this.commands = commands;
for(int i=0; i<commands.length; i++) {
if(commands[i] == null) {
commands[i] = new NoCommand();
}
}
}
public void excute() {
for(int i=0; i<commands.length; i++) {
commands[i].excute();
}
}
public void undo() {
for(int i=0; i<commands.length; i++) {
commands[i].undo();
}
}
}
遥控器:
public class RemoteControl {
private Command onButton[];
private Command offButton[];
private Command undoCommand;
public RemoteControl() {
onButton = new Command[7];
offButton = new Command[7];
for(int i=0; i<onButton.length; i++) {
onButton[i] = new NoCommand();
offButton[i] = new NoCommand();
}
undoCommand = new NoCommand();
}
public void setCommand(int slot, Command onCommand, Command offCommand) {
onButton[slot] = onCommand;
offButton[slot] = offCommand;
}
public void pushOnButton(int slot) {
onButton[slot].excute();
undoCommand = onButton[slot];
}
public void pushOffButton(int slot) {
offButton[slot].excute();
undoCommand = offButton[slot];
}
public void pushUndoButton() {
undoCommand.undo();
}
}
利用上面的思想我们还能实现一些炫酷的东西,例如队列,想想看,可以将不同的命令放入队列中,从队列拿命令出来的消费者并不需要知道拿出来的命令会执行什么操作,它只要去调用excute方法就可以了,调用完了再丢弃这个Command,继续获取下个Command。这样子工作队列类和进行具体计算操作的对象就完全是解耦的。
最后的最后,我们又来总结我们现在现有的设计模式武器。
面向对象基础
抽象、封装、多态、继承
六大设计原则
设计原则一:封装变化
设计原则二:针对接口编程,不针对实现编程
设计原则三:多用组合,少用继承
设计原则四:为交互对象之间的松耦合设计而努力
设计原则五:对扩展开放,对修改关闭
设计原则六:依赖抽象,不要依赖于具体的类
模式
命令模式:将“请求”封装成对象,以便使用不同的请求,队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。