花了两个个晚上的时间,将张老师的交通灯系统看了两遍,听张老师边说边写,貌似这个系统很好写似的,可是真正当自己来面对这样一个系统的时候,就不知所措了,就用面向对象分析的思想来分析该系统时,我们都无法分析的那么到位。看完两遍之后,我终于理清了思路,并做了详细的分析。
首先,我们要明白有12条路线。每个方向都有朝另外3个方向去的车辆,所以一共是12条。然后是每两个相对的路线的状态是相同的,比如从南到北和从北到南的车辆他们是具有相同状态的灯。系统要求不考虑向右的四盏灯,但是我们不能说这四条线上没有灯,我们可以假设这四盏灯是存在且长绿的。所以这12盏灯中只有4盏灯具有逻辑,就是说我们是需要控制这四盏灯的亮和灭就可以了。
Road类分析:代码如下:
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Road {
List<String> cars = new ArrayList<String>();
private String name=null;
public Road(String name){
this.name=name;
//这里是采用了线程池,当有任务进来时,执行器将任务交给线程池,再由线程池去决定到底使用哪个线程去解决问题
ExecutorService pool = Executors.newSingleThreadExecutor();//返回的就是一个线程池
pool.execute(new Runnable(){//执行任务
public void run(){
for(int i=1;i<1000;i++){
try {
Thread.sleep((new Random().nextInt(10)+1)*1000);//让线程随机休息1~10秒
} catch (InterruptedException e) {
e.printStackTrace();
}
cars.add(Road.this.name+"_"+i);//访问外部内的成员变量
}
}
});
ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);//定时器
timer.scheduleAtFixedRate(//调度线程池
new Runnable(){
public void run(){
if(cars.size()>0){
boolean lighted =Lamp.valueOf(Road.this.name).isLighted();//判断该路线上的灯的状态
if(lighted){
System.out.println(cars.remove(0)+"is traversing!");//把第一辆车移走
}
}
}
},
1, //过1秒后去执行任务
1, //执行完后再过1秒后继续执行
TimeUnit.SECONDS);
}
}
为什么对车辆操作要写在Road这个类中呢,这又让我想起了张老师的那句话,谁拥有数据,谁就对数据进行操作。车辆是Road的数据,所以添加一辆车或者一处一辆车都是由路来控制。用线程来模仿现实道路上的汽车过十字路口,很形象。定时器在这里起一个监控作用。
Lamp枚举:
public enum Lamp {
S2N("N2S", "S2W", false), S2W("N2E", "E2W", false), E2W("W2E", "E2S", false), E2S("W2N", "S2N", false), // 逻辑灯
N2S(null, null, false), N2E(null, null, false), W2E(null, null, false), W2N(null, null, false), //相反方向的灯
S2E(null, null, true), E2N(null, null, true), N2W(null, null, true), W2S(null, null, true);
private Lamp(String oppsite, String next, boolean lighted) {// 枚举的构造方法只能是私有的
this.oppsite = oppsite;
this.next = next;
this.lighted = lighted;
}
private boolean lighted;// 灯的状态
private String oppsite;//相反方向的灯,这里把下一站灯定义为字符串类型是为了解决第一个对象要用到后面还没有定义的对象的情况,再用valueof方法获取这个枚举对象
private String next;//下一盏灯
public boolean isLighted() {//判断灯的状态
return lighted;
}
public void light() {//亮灯
this.lighted = true;
if (oppsite != null)
Lamp.valueOf(oppsite).light();// 用valueof()方法得到对应的枚举对象
}
public Lamp blackout() {
this.lighted = false;
if (oppsite != null)
Lamp.valueOf(oppsite).blackout();
Lamp nextLamp = Lamp.valueOf(next);
if (next != null) {
nextLamp.light();//灭掉该盏 灯时点亮下一盏灯
}
return nextLamp;//将下下盏灯返回作为当前灯
}
}
使用枚举的好处是它规范了参数的形式,这样就可以不用考虑类型的不匹配,使程序得到了极大的简化。看第一遍视频的时候,我对这四盏灯的初始化很不解,N2S(null, null, false), N2E(null, null, false), W2E(null, null, false), W2N(null, null, false),我想为什么他们的oppsite是null而不是对应相反方向的灯呢,今天看第二遍想明白了,因为这四盏灯根本不需要自己控制,他们会随着对应相反方向灯的状态改变而改变,就是说他们的状态是一样的。他们根本没有逻辑。这里想清楚了,对if (oppsite != null)的判断也就随之明白了。
LampController类:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class LampController {
private Lamp currentLamp;// 当前灯
public LampController() {
currentLamp = Lamp.S2N;//从S2N开始
currentLamp.light();//并让它亮
ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);//产生一个线程,定时器
timer.scheduleAtFixedRate(new Runnable() {//调度任务
public void run() {
currentLamp = currentLamp.blackout();
}
},
10, // 10s执行
10, // 过10s后继续执行
TimeUnit.SECONDS);
}
}
LampController类主要是产生一个定时器,当12条路线初始化之后,调用灯控制器,并让其从S2N开始循环点亮和熄灭。
主类:
public class MainClass {
public static void main(String[] args) {
String[] directions = new String[]{
"S2N","S2W","E2W","E2S","N2S","N2E","W2E","W2N","S2E","E2N","N2W","W2S"
};
for(int i=0;i<directions.length;i++){//对12条路线进行初始化
new Road(directions[i]);
}
new LampController();//新建一个灯控制器,初始化并开始运行整个系统
}
}
我们做测试时用的是假设每条路上1000辆车,当所有路线的车走完之后,灯的状态会继续循环。
看完整个系统设计及编码视频之后,我在想软件设计就是为我们实际生活服务的,系统有什么特点,和生活中的相不相符,这就要我们平时多观察生活,比如说,红绿灯的亮的顺序。我们还需要思考,比如系统还有没有bug,该如何改进,在不同的场合下适不适用等等。也许对十字路口的该这样写,但是路口更多的情况呢?所以这些都是我们该考虑的问题。