您的位置:首页 > 职场人生

黑马程序员-java学习笔记之交通灯管理系统

2014-03-21 12:51 337 查看


------- android培训java培训、期待与您交流!-------

1.需求

模拟实现十字路口的交通灯管理系统逻辑,具体需求如下:

异步随机生成按照各个路线行驶的车辆。

例如:

由南向而来去往北向的车辆 ---- 直行车辆

由西向而来去往南向的车辆 ---- 右转车辆

由东向而来去往南向的车辆 ---- 左转车辆。。。

信号灯忽略黄灯,只考虑红灯和绿灯。

应考虑左转车辆控制信号灯,右转车辆不受信号灯控制。

具体信号灯控制逻辑与现实生活中普通交通灯控制逻辑相同,不考虑特殊情况下的控制逻辑。

注:南北向车辆与东西向车辆交替放行,同方向等待车辆应先放行直行车辆而后放行左转车辆。

每辆车通过路口时间为1秒(提示:可通过线程Sleep的方式模拟)。

随机生成车辆时间间隔以及红绿灯交换时间间隔自定,可以设置。

不要求实现GUI,只考虑系统逻辑实现,可通过Log方式展现程序运行结果。

2.需求分析

首先画一个十字路口的图片,把所有的路线图在图上标注出来:



上面一共有十二条线路,在平时生活中,右拐弯是一直通行的,在此假设右拐弯的都是绿灯。然后还有八条线路,剩下的这八条线路的灯都是成对亮的,可以归为4组,东-西两组,南-北两组,所以,程序只需考虑图中标注了数字号的4条路线的控制灯的切换顺序,这4条路线相反方向的路线的控制灯跟随这4条路线切换,不必额外考虑。

我们初步设想一下有哪些对象:红绿灯,红绿灯的控制系统,汽车,路线。汽车看到自己所在路线对应的灯绿了就穿过路口吗?不是,还需要看其前面是否有车,看前面是否有车,该问哪个对象呢?该问路,路中存储着车辆的集合,显然路上就应该有增加车辆和减少车辆的方法了。再看题目,我们这里并不要体现车辆移动的过程,只是捕捉出车辆穿过路口的过程,也就是捕捉路上减少一辆车的过程,所以,这个车并不需要单独设计成为一个对象,用一个字符串表示就可以了。

所以系统中存在的对象应该有12个路灯对象,十二个路线对象,和一个路灯控制器对象。

2.类的编写

Road类:

每一个Road对象都有名称,定义私有变量:路的名字。

每条路上都有车,车必须有顺序的添加和移除:用ArrayList来保存车。

每一个车的名字起名字。路线的名字 + 车的编号。

每创建一条路线,就有了路线的名字:构造方法要接受名称参数。

创建车辆:假设每一秒种,这条路上就出现有一辆车,所以要在构造方法内部不停的创建车,利用for循环来创建。

但是不能一次性创建1000辆,不然一瞬间有1000辆是不现实的,利用随机数,Thread.sleep随机休息一会再生成车。

但是:对象创建期间,如果Thread.Sleep,对象不返回的,这么对象根本创建不出来。利用线程即可解决,线程在方法结束之后还可以运行!

所以可以在构造方法内部使用线程技术进行生成车辆。利用java5中提供的线程池来操作线程,

在每一个线程中利用for循环生成车辆,线程睡眠一个随机数。希望一到10秒随机数内创建一辆车。Random.nextInt(10)+1 * 1000,一秒到十秒了。

移走车辆:车每过一秒,去看自己灯是否是绿的,是绿的就要把第一个车移走。利用定时器。利用Executors.newScheduledThreadPool(1)调度池。返回ScheduleExecutorService.,timer。利用固定频率定时器

定时器传入一个Runnable任务,一个初始化延迟时间,一个频率,一个时间单元。每过一秒就会去执行Runnable任务。

检查当前路的灯是否为绿,检查是否有车,如果有车,如果灯是绿的,则让前面的车开走。移除并打印。

道路的最终代码:

/**
* 道路类:可以在1~10S内随机的生成车辆。并可以通过路口。
* @author real
*
*/
public class Road {

//List用来保存道路上的车辆
private List<String>  vehicle = new ArrayList<String>();
//道路的名称
private String name;
//创建Road时必须传入路名

public Road(String name) {
//设置道路名为传入的名称
this.name = name;
//创建一个单线程,生成车辆
ExecutorService pool = Executors.newSingleThreadExecutor();

//线程来执行任务
pool.execute(new Runnable(){
@Override
public void run() {
for(int i = 0; i < 1000; i++) {
try {
//随机休息1~10s
Thread.sleep((new Random().nextInt(10) + 1) * 1000);

//访问外部类的变量,用外部类的类名.this.变量名
vehicle.add(Road.this.name + "::" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}});

//生成一个定时器线程池
ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);
//定时器调度,并且以固定频率执行任务
timer.scheduleAtFixedRate(
new Runnable(){

@Override
public void run() {
//查看自己的灯是否是绿的,如果是绿的,则把车移动走
Lamp lamp = Lamp.valueOf(Road.this.name);

if(lamp.isLighted()) {
//如果道路上有车,则让车通过。
if(!vehicle.isEmpty()) {
System.out.println(vehicle.remove(0) + " 通过路口!");
}
}
}
},
1,
1,
TimeUnit.SECONDS);
}
}


Lamp类:

在交通灯中一共有十二个灯,而且数量时固定的。

在程序的其他地方要根据灯的名称就可以获得对应的灯的实例对象。

综合这些因素,将Lamp类用java5中的枚举形式定义更为简单。

每个Lamp对象中的亮黑状态用lighted变量表示,选用S2N、S2W、E2W、E2N这四个方向上的Lamp对象依次轮询变亮。

Lamp对象中还要有一个opposite变量来表示它们相反方向的。

再用一个next变量来表示此灯变亮后的下一个变亮的灯。

这三个变量用构造方法的形式进行赋值,因为枚举元素必须在定义之后引用,所以无法再构造方法中彼此相互引用,所以,相反方向和下一个方向的灯用字符串形式表示。

增加让Lamp变亮和变黑的方法:light和black,对于S2N、S2W、E2W、E2N这四个方向上的Lamp对象,这两个方法内部要让相反方向的灯随之变亮和变黑,black方法还要让下一个灯变亮,同时要返回下一个亮着的灯给道路使用。

除了S2N、S2W、E2W、E2N这四个方向上的Lamp对象之外,其他方向上的Lamp对象的next和opposite属性设置为null即可。

并且S2N、S2W、E2W、E2N这四个方向上的Lamp对象的next和opposite属性必须设置为null,如果不为null,则在点亮灯时,会一直点亮反方向的灯,会一直死循环下去了。

右拐方向的灯一直为亮的,且没有相反方向的和下一个等。所以灯控系统实际控制的就是S2N、S2W、E2W、E2N这四个方向的灯。其对应的四个灯也是控制这四个灯时对应的控制的。

Lamp类的源代码:

/**
* 灯的枚举。一共有十二个灯,其中,四个执行,四个左拐,四个右拐。
* @author real
*
*/
public enum Lamp {

/**
* S:南,N:北。W:西.E:东
* 	南-北:S2N,南-西:S2W.东-西:E2W,东-南:E2S
* 	北-南:N2S, 北-东:N2E,西-东:W2E,西-北:W2N
* 右拐:西-南:W2S,南-东:S2E,东-北:E2N,北-西:N2W
*/

S2N("N2S", false, "S2W"), S2W("N2E", false, "E2W"), E2W("W2E", false, "E2S"), E2S("W2N", false, "S2N"),
N2S(null, false, null), N2E(null, false, null), W2E(null, false, null), W2N(null, false, null),
W2S(null, true, null), S2E(null, true, null), E2N(null, true, null), N2W(null, true, null);

/**
* 构造方法
* @param opposite 反方向的灯
* @param lighted  是否是亮的
* @param nextLamp  下一个灯
*/
private Lamp(String opposite, boolean lighted, String next){
this.lighted = lighted;
this.opposite = opposite;
this.next = next;
}

//灯是否亮
private boolean lighted;
//反方向的灯
private String opposite;
//下一个灯
private String next;

/**
* 检测灯是否是亮的
* @return
*/
public boolean isLighted(){
return this.lighted;
}

/**
* 点亮灯
*/
public void light(){
System.out.println(this + "变亮了");

//自己亮
lighted = true;
//对面的灯也要亮
if(opposite != null) {
Lamp.valueOf(opposite).light();
}
}

/**
* 熄灭灯
*/
public Lamp black(){
System.out.println(this + "变灭了");

//自己灭
lighted = false;
//对面的灯也要灭
if(opposite != null) {
Lamp.valueOf(opposite).black();
}

Lamp nextLamp = null;
//点亮下一个灯,并返回。
if(next != null) {
nextLamp = Lamp.valueOf(next);
nextLamp.light();
}

return nextLamp;
}

}


灯控系统:

LampController,控制灯的亮,有一个变量,表示当前的灯:Lamp currentLamp。

在构造方法。要有一个初始化灯,并且让当前的灯变绿。

每隔十秒,就让当前灯变红,并让下一个灯变绿。并把当前灯指向新的变绿的灯。

在灯内部,变黑方法,同时把下一个变绿的灯返回。
LampController:
/**
* 灯控系统
* @author real
*
*/
public class LampController {

private Lamp currentLamp;

public LampController() {
//初始化灯为S2N
currentLamp = Lamp.S2N;

//定义一个定时器
ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);

//启动线程,延迟1S启动任务,每隔十秒执行一次,把当前灯变灭,并把变亮的灯设置为当前的灯。
timer.scheduleAtFixedRate(
new Runnable(){
@Override
public void run() {
currentLamp = currentLamp.black();
}
},
1,
10,
TimeUnit.SECONDS);
}

}


3.系统测试

所有的类编写完毕,编写测试方法来进行测。

生成十二条路,这十二条线路必须和路灯中的十二个名字相同。在此利用数组存储对应的十二字符串,然后在for循环中生成十二条路线。启动控制系统,程序即可运行。LampTest:

public class LampTest {

public static void main(String[] args) {

//把十二条路线的名字作为一个数组
String[] roads ={"S2N", "S2W", "E2W", "E2S",
"N2S", "N2E", "W2E", "W2N",
"W2S", "S2E", "E2N", "N2W"};

//生成十二条线路。
for(int i = 0; i < roads.length; i++) {
new Road(roads[i]);
}

//启动灯控系统
new LampController();
}

}
在每一次的通行中,只能有六个方向进行通行(始终有四个左拐):结果如下:

S2N变灭了
N2S变灭了
S2W变亮了
N2E变亮了
W2S::0 通过路口!
N2E::0 通过路口!
E2N::0 通过路口!
S2E::0 通过路口!
W2S::1 通过路口!
E2N::1 通过路口!
N2W::0 通过路口!
E2N::2 通过路口!
N2W::1 通过路口!
S2W::0 通过路口!
S2W变灭了
N2E变灭了
E2W变亮了
W2E变亮了


看了张孝祥老师的视频,在逐渐明白面向对象的精髓,设计好一个系统是如此的重要。在此非常感谢张孝祥老师。希望早日进黑马深造,使自己的能力更上一层楼。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: