Java内部类实例:控制框架(e.g.温室控制系统)

       应用程序框架(application framework)是被设计用来解决某类特定问题的一个类或一组类。要运用某个应用程序框架,通常是继承一个或多个类,并覆盖某些方法。在覆盖后的方法中,编写代码定制应用程序提供的通用解决方案,以解决你的特定问题。模板方法包含算法的基本结构,并且会调用一个或多个可覆盖的方法,以完成算法的动作。设计模式总是将变化的事物与保持不变的事物分离开,在这个模式中,模板方法是保持不变的事物,而可覆盖的方法就是变化的事物。

       控制框架(control framework)是一类特殊的应用程序框架,它用来解决相应事件的需求。主要用来响应事件的系统被称作事件驱动系统。

       在本文中,保持不变的事物即模版方法是控制框架,变化的事物即可覆盖的方法是温室控制系统。

       一、控制框架模版

       我们首先需要定义一个事件(Event)文件,事件文件中主要包含事件的”就绪”状态,以判断什么时候开始执行相对应的动作,但是动作我们写成抽象(abstract)方法即可,让子类去具体实现其方法。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package content.ch10;

public abstract class Event {
private long eventTime;
protected final long delayTime;

public Event(long delayTime) {
this.delayTime = delayTime;
start();
}

public void start() {
eventTime = System.nanoTime() + delayTime;
}

public boolean ready() {
return System.nanoTime() >= eventTime;
}

public abstract void action();
}

       上述事件代码依据构造函数中的延迟时间(delayTime)变量来决定什么时候触发事件,start()是一个独立的方法,而没有包含在构造器内,因为这样就可以在事件运行以后重新启动计时器,也就是能够重复使用Event对象。例如,如果想要重复一个事件,只需简单地在action()中调用start()方法。ready()告诉你何时能够运行action()方法,当然,也可以在导出类中覆盖ready()方法,使得Event能够基于时间之外的其他因素而触发。

       接下来,我们需要写我们的事件的控制器(实际控制框架),以用来管理并触发事件的发生。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package content.ch10;

import java.util.ArrayList;
import java.util.List;

public class Controller {
private List<Event> eventList = new ArrayList<Event>();

public void addEvent(Event c) {
eventList.add(c);
}

public void run() {
while (eventList.size() > 0) {
// Make a copy so you're not modifying the list
// while you're selecting the elements in it.
for (Event e : new ArrayList<Event>(eventList)) {
if (e.ready()) {
System.out.println(e);
e.action();
eventList.remove(e);
}
}
}
}

/////////////////////////// Backup///////////////////////////////
public void run_V1() {
while (eventList.size() > 0) {
// 如果不做eventList的备份,eventList.get(0)之后,remove(0)操作后,
// 原来的索引1移到索引0处,原来的索引2移到索引1处,那么第二次循环取索引1的操作,
// 会变成取原来的索引2,即跳过了真正的索引1。即选择其中的元素后,还要进行删除操作,
// 会影响第二次取值,所以需要进行一个备份。
for (int i = 0; i < eventList.size(); i++) {
if (eventList.get(i).ready()) {
System.out.println(eventList.get(i));
eventList.get(i).action();
eventList.remove(i);
}
}
}
}

}

       上述中事件的列表要做备份,Backup后面已经解释清楚了原因。

       run()方法循环遍历eventList,寻找就绪的(ready())、要运行的Event对象。对找到的每一个就绪的(ready())事件,使用对象的toString()打印其信息,调用其action()方法,然后从队列中移除此Event。

       在目前的设计中,我们只是把控制框架写好了,包括事件(Event)和控制器(Controller),但你并不知道Event到底做了什么,这正是此设计的关键所在,“使变化的事物与不变的事物分离”。“变化的事物”就是各种不同的Event对象所具有的不同行为,而你可以通过创建不同的Event子类来表示不同的行为。

       而这正是内部类擅长之处,内部类允许:

       (1).控制框架的完整实现是由单个的类创建的,从而使得实现的细节被封装了起来。内部类用来表示解决问题所必需的各种不同的action()。

       (2).内部类能够很容易地访问外围类的任意成员,所以可以避免这种实现变得笨拙。

       二、温室控制系统

       通过此控制框架的一个特定实现:温室控制系统,来说明内部类的具体作用。

       此温室控制系统可以控制温室的运作,比如:控制灯光、水、温度调节器的开关,以及响铃和重新启动系统,每个行为都是完全不同的。控制框架的设计使得分离这些不同的代码变得非常容易,使用内部类,可以在单一的类里面产生对同一个基类Event的多种导出版本。对温室系统的每一种行为,都继承一个新的Event内部类,并在要实现的action()中编写控制代码。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package content.ch10;

public class GreenhouseControls extends Controller {
private boolean light = false;

public class LightOn extends Event {
public LightOn(long delayTime) {
super(delayTime);
}

public void action() {
// Put hardware control code here to
// physically turn on the light.
light = true;
}

public String toString() {
return "Light is on";
}
}

public class LightOff extends Event {
public LightOff(long delayTime) {
super(delayTime);
}

public void action() {
// Put hardware control code here to
// physically turn off the light.
light = false;
}

public String toString() {
return "Light is off";
}
}

private boolean water = false;

public class WaterOn extends Event {
public WaterOn(long delayTime) {
super(delayTime);
}

public void action() {
// Put hardware control code here
water = true;
}

public String toString() {
return "Greenhouse water is on";
}
}

public class WaterOff extends Event {
public WaterOff(long delayTime) {
super(delayTime);
}

public void action() {
// Put hardware control code here
water = false;
}

public String toString() {
return "Greenhouse water is off";
}
}

private String thermostat = "Day";

public class ThermostatNight extends Event {
public ThermostatNight(long delayTime) {
super(delayTime);
}

public void action() {
// Put hardware control code here.
thermostat = "Night";
}

public String toString() {
return "Thermostat on night setting";
}
}

public class ThermostatDay extends Event {
public ThermostatDay(long delayTime) {
super(delayTime);
}

public void action() {
// Put hardware control code here.
thermostat = "Day";
}

public String toString() {
return "Thermostat on day setting";
}
}

// An example of an action() that inserts a
// new one of itself into the event list.
public class Bell extends Event {
public Bell(long delayTime) {
super(delayTime);
}

public void action() {
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);
}
}

public void action() {
for (Event e : eventList) {
e.start(); // Rerun each event
addEvent(e);
}
start(); // Rerun this Event
addEvent(this);
}

public String toString() {
return "Restarting system";
}
}

public static class Terminate extends Event {
public Terminate(long delayTime) {
super(delayTime);
}

public void action() {
System.exit(0);
}

public String toString() {
return "Terminating";
}
}
}

       在实际中,可以在action()中加入相对应的硬件控制代码来实际上控制相应的物理行为。

       最后,我们需要配置该温室控制系统,通过添加各种不同的Event对象来进行配置。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package content.ch10;

public class GreenhouseController {
public static void main(String[] args) {
GreenhouseControls gc = new GreenhouseControls();
//Instead of hard-wiring,you could parse
//configution information from text file here.
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(800),
gc.new ThermostatDay(1400)
};
gc.addEvent(gc.new Restart(2000,eventList));


/* // 从命令行接受输入
if(args.length == 1){
gc.addEvent(new GreenhouseControls.Terminate(new Integer(args[0])));
}*/

gc.addEvent(new GreenhouseControls.Terminate(new Integer(100)));
gc.run();
}
}

       这个类的作用是初始化系统,它添加了所有的相应的事件。Restart事件反复运行,而且它每次都会将eventList加载到GreenhouseControls对象中。如果提供了命令行参数,系统会以它作为毫秒数,决定什么时候终止程序。运行该程序结果如下:

1
2
3
4
5
6
7
8
9
Bing!
Thermostat on night setting
Light is on
Light is off
Greenhouse water is on
Greenhouse water is off
Thermostat on day setting
Restarting system
Terminating

       至此,我们应该能很清楚的意识到内部类的实际作用,内部类很像多重继承,比如:Bell和Restart拥有Event的所有方法,并且似乎也拥有外围类GreenhouseControls的所有方法。使用内部类,我们可以很优雅的实现温室控制系统,通过模板(不变的事物)—控制框架,具体实现(变化的事物)—温室控制系统,我们很好的实现了温室控制系统的功能,并且当我们后期想要加一些功能,比如:加一个打开、关闭风扇的功能,我们只需在GreenhouseControls.java中增加两个控制风扇打开、关闭的内部类即可,然后在GreenhouseController.java中配置以使用新加的Event对象即可,对于控制框架模板完全不需要修改任何地方。

       来自:《Java编程思想》第十章 内部类与控制框架