这篇文章呢,咱们来学习一下命令模式,一样地咱们会从一个例子入手(对《Head First 设计模式》这本书上的例子进行了稍微地修改),经过三个版本的迭代演进,让咱们能更好地理解命令模式。java
如今有一个装修公司,在装修房子时会安装一个家用电器的总控制器,例若有电灯、空调、热水器、电脑等电器,这个控制器上的每一对 ON/OFF 开关就对应了一个具体的设备,能够对该设备进行操做。设计模式
另外,有些用户家中可能没有热水器,不须要对其进行控制,而有些用户家中可能还有电视,又须要对电视进行控制。因此,具体对哪些设备进行控制,须要由用户本身决定。试想一下,这个系统该如何设计呢?数组
咱们先来尝试一下。例如,如今须要对电灯、空调、电脑进行控制,这三个实体类定义以下(注意它们是由不一样的厂家制造,其接口不一样):ide
public class Lamp {
// 接口不一样,也就是开关的方法不一样
public void turnOn() {
System.out.println("打开电灯");
}
public void turnOff() {
System.out.println("关闭电灯");
}
}
public class AirConditioner {
public void on() {
System.out.println("打开空调");
}
public void off() {
System.out.println("关闭空调");
}
}
public class Computer {
public void powerOn() {
System.out.println("打开电脑");
}
public void powerOff() {
System.out.println("关闭电脑");
}
}
复制代码
对于控制器呢,因为咱们事先不知道具体的槽上,对应的是什么设备。因此,咱们只能一个一个地进行判断,而后才能执行开关操做。学习
public class SimpleController1 {
// Object 类型的数组
private Object[] control = new Object[3];
public void setControlSlot(int slot, Object controller) {
control[slot - 1] = controller;
}
// 使用 instanceOf 判断类型
public void onButtonWasPressed(int slot) {
if (control[slot - 1] instanceof Lamp) {
Lamp lamp = (Lamp) control[slot - 1];
lamp.turnOn();
} else if (control[slot - 1] instanceof AirConditioner) {
AirConditioner airConditioner = (AirConditioner) control[slot - 1];
airConditioner.on();
} else if (control[slot - 1] instanceof Computer) {
Computer computer = (Computer) control[slot - 1];
computer.powerOn();
}
}
public void offButtonWasPushed(int slot) {
if (control[slot - 1] instanceof Lamp) {
Lamp lamp = (Lamp) control[slot - 1];
lamp.turnOff();
} else if (control[slot - 1] instanceof AirConditioner) {
AirConditioner airConditioner = (AirConditioner) control[slot - 1];
airConditioner.off();
} else if (control[slot - 1] instanceof Computer) {
Computer computer = (Computer) control[slot - 1];
computer.powerOff();
}
}
}
复制代码
下面写个类来测试一下:测试
public class Test {
public static void main(String[] args) {
// 三种家电
Lamp lamp = new Lamp();
AirConditioner airConditioner = new AirConditioner();
Computer computer = new Computer();
// 设置到相应的控制槽上
SimpleController1 simpleController1 = new SimpleController1();
simpleController1.setControlSlot(1, lamp);
simpleController1.setControlSlot(2, airConditioner);
simpleController1.setControlSlot(3, computer);
// 对 1 号槽对应的设备进行开关操做
simpleController1.onButtonWasPressed(1);
simpleController1.offButtonWasPushed(1);
}
}
// 打开电灯
// 关闭电灯
复制代码
对于上面的这种方式,因为没法预先知道控制器上的槽对应的什么设备,因此控制器的实现中使用了大量的类型判断语句,咱们能够看到,这样的设计很很差。this
另外,若是有别的用户想要控制其余设备,就须要去修改控制器的代码,这明显不符合开闭原则,而且会形成很大的工做量。spa
那该如何进行改进呢?咱们想着要是这些设备的接口能够修改就行了,咱们将它们的接口修改为统一的,也就不须要再去一个一个地判断了。线程
来看一下它如何实现,咱们定义一个家电接口,其中包含开关操做,而后让不一样的家电设备去实现它。设计
public interface HomeAppliance {
void on();
void off();
}
public class Lamp implements HomeAppliance {
@Override
public void on() {
System.out.println("打开电灯");
}
@Override
public void off() {
System.out.println("关闭电灯");
}
}
public class AirConditioner implements HomeAppliance {
@Override
public void on() {
System.out.println("打开空调");
}
@Override
public void off() {
System.out.println("关闭空调");
}
}
public class Computer implements HomeAppliance {
@Override
public void on() {
System.out.println("打开电脑");
}
@Override
public void off() {
System.out.println("关闭电脑");
}
}
复制代码
如此,控制器就能够这样设计:
public class SimpleController2 {
// 三种家电,统一的接口
private HomeAppliance[] control = new HomeAppliance[3];
public void setControlSlot(int slot, HomeAppliance controller) {
control[slot - 1] = controller;
}
// 不须要再进行判断
public void onButtonWasPressed(int slot) {
control[slot - 1].on();
}
public void offButtonWasPushed(int slot) {
control[slot - 1].off();
}
}
复制代码
下面写段代码来测试一下:
public class Test {
public static void main(String[] args) {
HomeAppliance lamp = new Lamp();
HomeAppliance airConditioner = new AirConditioner();
HomeAppliance computer = new Computer();
SimpleController2 simpleController2 = new SimpleController2();
simpleController2.setControlSlot(1, lamp);
simpleController2.setControlSlot(2, airConditioner);
simpleController2.setControlSlot(3, computer);
simpleController2.onButtonWasPressed(1);
simpleController2.offButtonWasPushed(1);
}
}
复制代码
能够看到,咱们不须要再写大量的类型判断语句,而且有用户想要控制别的设备时,只须要让该设备实现 HomeAppliance 接口,就能够了。
但理想很丰满,显示很苦干。惋惜的是这些家电设备的接口从出厂时就已经固定了,没法再改变,这种方式只是看起来不错,咱们还须要另寻出路。
咱们继续进行改进。那咱们可否将这些设备包装一下,让其对外提供统一的开关方法,如此控制器就不须要去判断是什么类型,而是只管去调用包装后的开关方法就行了。
也就是说从新定义一个统一的接口,它包含了开关操做的方法,而后让不一样的设备,都建立一个与它本身对应的类,用来操做它自己。
对于三个实体类,咱们仍然使用第一次尝试时使用的类。而这个统一的接口能够这样定义:
public interface OnOff {
void on();
void off();
}
复制代码
而后,让不一样的设备,都建立一个与它本身对应的类,其内部封装了它本身。在对外提供的统一方法 on/off 实现中,再去调用本身的开关方法:
public class LampOnOff implements OnOff {
private Lamp lamp;
public Lamp_OnOff(Lamp lamp) {
this.lamp = lamp;
}
@Override
public void on() {
lamp.turnOn();
}
@Override
public void off() {
lamp.turnOff();
}
}
public class AirConditionerOnOff implements OnOff {
private AirConditioner airConditioner;
public AirConditioner_OnOff(AirConditioner airConditioner) {
this.airConditioner = airConditioner;
}
@Override
public void on() {
airConditioner.on();
}
@Override
public void off() {
airConditioner.off();
}
}
public class ComputerOnOff implements OnOff {
private Computer computer;
public Computer_OnOff(Computer computer) {
this.computer = computer;
}
@Override
public void on() {
computer.powerOn();
}
@Override
public void off() {
computer.powerOff();
}
}
复制代码
这时控制器就能够这样写,和版本 2 很相似:
public class SimpleController3 {
private OnOff[] onOff = new OnOff[3];
public void setControlSlot(int slot, OnOff controller) {
onOff[slot - 1] = controller;
}
public void onButtonWasPressed(int slot) {
onOff[slot - 1].on();
}
public void offButtonWasPushed(int slot) {
onOff[slot - 1].off();
}
}
复制代码
下面写段代码来测试一下:
public class Test {
public static void main(String[] args) {
Lamp lamp = new Lamp();
AirConditioner airConditioner = new AirConditioner();
Computer computer = new Computer();
// 三种设备封装成统一的接口
// 也就是三种命令对象
OnOff lampOnOff = new LampOnOff(lamp);
OnOff airConditionerOnOff = new AirConditionerOnOff(airConditioner);
OnOff computerOnOff = new ComputerOnOff(computer);
SimpleController3 simpleController3 = new SimpleController3();
simpleController3.setControlSlot(1, lampOnOff);
simpleController3.setControlSlot(2, airConditionerOnOff);
simpleController3.setControlSlot(3, computerOnOff);
simpleController3.onButtonWasPressed(1);
simpleController3.offButtonWasPushed(1);
}
}
复制代码
上面这种作法呢,既没有了大量的判断语句,并且用户想要控制其余设备时,只须要建立一个实现 OnOff 接口的类,在这个类的 on、off 方法中,调用设备的具体实现便可。
其实上面的版本三就是命令模式,咱们这就来看一下在 《Head First 设计模式》中对它的定义:它将“请求”封装成命令对象,以便使用不一样的请求、队列或者日志来参数化其余对象。命令模式也支持可撤销操做。
对于这个定义如何理解呢?咱们以上面的例子来讲明。
在接收者(电灯)上绑定一组开关动做(turnOn/turnOff 方法)就是请求,而后将请求封装成一个命令对象(OnOff 对象),它对外只暴露 on/off 方法。
当命令对象(OnOff 对象)的 on/off 方法被调用时,接收者(电灯)就会执行相应的动做(turnOn/turnOff 方法)。对于外界来讲,其余对象不知道究竟哪一个接收者执行了动做,而是只知道调用了命令对象的 on/off 方法。
在将请求封装成命令对象后,就能够用命令来参数化其余对象,这里就是控制器的插槽(OnOff[])用不用的命令(OnOff 对象)当参数。
它的 UML 图以下:
下面总结一下命令模式的优势:
缺点:
对于线程池(这里咱们先不考虑线程数小于核心线程数的状况),咱们将任务(命令)添加到阻塞队列(工做队列)的某一端,而后线程从另外一端获取一个命令,调用它的 run 方法执行,等待这个调用完成后,再取出下一个命令,继续执行。
命令(任务)接口的定义以下。而具体的任务由咱们本身实现:
public interface Runnable {
public abstract void run();
}
复制代码
在线程池 ThreadPoolExecutor 中有一个阻塞队列,用于存听任务,它的部分源码以下:
public class ThreadPoolExecutor extends AbstractExecutorService {
// 存放命令
private final BlockingQueue<Runnable> workQueue;
// 注意:这里与上面说的例子中 execute 方法不一样
public void execute(Runnable command) {
···
// 线程数大于核心线程数,将命令加入到阻塞队列
if (isRunning(c) && workQueue.offer(command)) {
···
// 建立 worker
addWorker(null, false);
}
···
}
}
复制代码
在调用 ThreadPoolExecutor 的 execute 方法时,会将实现命令接口的任务添加到阻塞队列中。
最终线程在执行 Worker 的 run 方法时,又会调用外部的 runWorker 方法,它会循环从阻塞队列中一个一个地获取命令对象,而后调用命令对象的 run 方法执行,一旦完成后,就会再去处理下一个命令对象:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock();
try {
// 循环调用 getTask 获取命令对象
while (task != null || (task = getTask()) != null) {
w.lock();
try {
try {
// 调用命令对象的 run 方法执行
task.run();
} ···
} finally {
task = null;
w.unlock();
}
}
} ···
}
复制代码
这里简单地说了一下,具体线程池的实现,感兴趣的小伙伴能够本身研究一下。