黑马程序员_7K面试题之交通灯系统

时间:2023-02-17 20:16:35

                                            交通灯信号模拟系统


一、概述

模拟实现十字路口的交通灯管理系统逻辑,具体需求如下:(需求直接来源于老师的文档)

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

例如:

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

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

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

       。。。

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

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

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

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

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

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

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



二、需求分析:

纵观整个系统的需求我们可以得出以下结论:在一个十字路口,涉及到了车、灯以及路,可以把他们之间的关系模拟以下,把路设计成为一个存储车辆的容器,车辆来了,存入容器内,等到车辆将要通过某一条路上的红绿灯亮起,车辆启动,相当于从容器中取出车辆,通过十字路口,下面再仔细地介绍:

黑马程序员_7K面试题之交通灯系统

四面拐弯的设为永远都是绿灯,也就是说四面什么时候都可以转,也就是S--E ,E--N,N--W,W--N,所以我们在下面就不加以考虑了

我们先从S---N看起:假设有S--N的车可以开动了,那么这个时候就有N--S的车也可以开动了,也就是说只要S--N可以通车,那么N--S也可以通车

S--N的绿灯停止了,S--N将不能再通车,同样,N--S也不能通车。

下一个绿灯就是S--W:S--W可以通车,则N--E也可以通车

S--W绿灯停止:下一个绿灯是E--W:E--W可以通车,W--E也可以通车

E--W绿灯停止:下一个绿灯是E--S;E--S可以通车,W--N也可以通车

E--S绿灯停止:下一个绿灯又回到S--N。如此循环。

从上面的分析可以得出,我们只需要四个方向上的交通灯状态就可以控制整个交通系统,因为我们发现他们都是成对出现,比如说我们只要知道S--N是绿灯,那么N--S也应该是绿灯,S--W是绿灯,N--E也应该是绿灯,还有四个角上的拐弯也固定的绿灯,所以我们只需控制四个方向上的四盏等就可以控制整个系统了。


三、面向对象的分析与设计

①每一条路上的车辆在某一时段内可以随机增加车辆,在绿灯到来的时候,要减少该路上的车辆数。当然是在绿灯亮起期间有顺序减少车辆。

某个时刻,可能有任意的车辆到任意的一条路上,这里我们使用到了12盏灯,所有我们要假设有十二条路(实际上只有8条),且每条上某个时刻都有可能有车辆加入该路上等待(或者直接通过,但是得是绿灯且其前面没有车的时候),也就是说我们要为每一盏灯创建一个存储车辆的容器,当某一盏灯亮起的时候,对应容器中的汽车就可以通过了,

②每一条路上在指定时间都回去检查该线路上红绿灯是否为绿。不为绿,不允许车辆通过本线路,在某一条线路上的红绿灯表红的时候,要记得把下一个方向上的红绿灯变绿。

设计一个Lamp类来表示一个交通灯,每个交通灯都维护一个状态:亮(绿)或不亮(红)
总共有12条路线,所以,系统中总共要产生12个交通灯。右拐弯的交通灯为常绿状态,即永远不变红。
无论在程序的什么地方去获得某个方向的灯时,每次获得的都是同一个实例对象,所以Lamp类改用枚举来做显然具有很大的方便性,永远都只有代表12个方向的灯的实例对象。
设计一个枚举类,它定时让当前的绿灯变红。

四 用到的类的编写

①Road类的编写

由于有12条线路,这里开启一个线程池,分配线程池中的线程去为每一条线路增加车辆

同时还要监视红绿灯(以便知道那个线路上的车辆可以启动),那就要开启一个线程去监视该红绿灯的情况,同时在监视中还要得到下一个变绿的灯。

package cn.itcast.Traffic;
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 {
//路就好比是以个容器,在路上面有车

private List<String> ArrRoad = new ArrayList<String>();
//面相接口编程,提高程序的可扩展性

String name = null; //指定是那条路上的的产生的车辆



public Road(String name ){
this.name = name;
//可以产生车辆了
//由于是在某个时刻,每个方向上都有可能产生车辆,所以这里要用到多线程的思想
ExecutorService thread = Executors.newCachedThreadPool(); //创建线程池
thread.execute(new Runnable() {//任务到来时,从线程池中选择一个线程来执行该任务

@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 1000; i++) {
//为了达到更加接近实际,这里设置一个随机数,也就是说在某个时间段内随时都有可能有汽车开到路上
try {

Thread.sleep((new Random().nextInt(10)+1)*1000);//在1s--10s中有车上路
ArrRoad.add(Road.this.name+(i+1));//指定是那条路上的第几辆车,
//这里有个知识点,匿名内部类访问外围类的成员变量的时候,有两种方法,可以将外围类的成员变量设置为final,也也可像这里写的这样
//类名.this.成员变量
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}



}

}
});
//我是这样想的,某条路上有车上来,当遇到路灯的时候,那么这条路上的车就会开走,也就是说这条路上的车会减少

//这里也要单独开启一个线程来执行个监视(监视绿灯)
//dispatch(调度)
// 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。这里就相当于说是规定什么时候去观察红绿灯的情况
ScheduledExecutorService dispatch= Executors.newScheduledThreadPool(1);
//这里的意思是在 创建并执行一个在给定初始延迟1s后首次启用的定期操作,在1s后有执行new Runnable的实现类对象的方法

dispatch.scheduleAtFixedRate(
new Runnable() {

@Override
public void run() {
// TODO Auto-generated method stub
//先判断判断,name路上的灯是否亮了
boolean light =Lamp.valueOf(Road.this.name).isstate();

//在这里想要知道那条公路上的车该走了,首先得到该路上的车所要走的那条路线上的灯
if(ArrRoad.size() > 0){
if(light){
System.out.println(Road.this.name+"路上的车"+ArrRoad.remove(0)+"飞快通过");

}
}


}
},
1,
1,
TimeUnit.SECONDS
);

}

}

②Lamp类的编写

系统中有12个方向上的灯,在程序的其他地方要根据灯的名称就可以获得对应的灯的实例对象,综合这些因素,将Lamp类用java5中的枚举形式定义更为简单。
每个对象中与当前对象所代表的的灯相对应的灯用correlight表示,该灯是否有下一个灯用next表示,当前灯是否为绿灯用flag表示。
增加了三个方法,也就是开启绿灯,关闭绿灯以及获取当前灯的状态是绿灯还是红灯。
在这里,我设置了Lamp的对象的三个参数的意义分别为:是否有与之对应的灯,为null者表示没有,是否有下一个灯,为null表示没有,当前灯是否为绿,没false表示为红灯。
实现细节在源代码中讲述的很清楚:
package cn.itcast.Traffic;



//使用枚举来描述每个路口路口上的灯
//这里我们先从南到北说起,S,N,E,W代表南、北、东、西,S2N即使南到北
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);
//这里有必要解释一下S2N("N2S","S2W",false):
//参数一指定了与当前灯向对应的灯,参数二,指定该灯的下一个灯,
//参数三,指定该灯是否亮起绿灯
//corresponding(对应)
String correlight = null;
String next = null;
boolean flag;
Lamp nextlight;
Lamp(String correlight,String next,boolean flag ){
this.correlight = correlight;
this.next = next;
this.flag = flag;
}
public boolean isstate(){
return flag;

}
//指定开启那条路上的绿灯
public void oppen(){
flag = true;
if(correlight != null){ //判断当前要开启的灯是否有与之对应的灯,有,则开启,没有则不用开启,也防止的死循环
Lamp.valueOf(correlight).oppen();//这里的意思是得到当前对象对应的灯并打开该绿灯
//Lamp.valueOf(String str )返回带指定名称的指定枚举类型的枚举常量。这里得到的也就是和correlight同名的枚举常量
 }

}
//指定关闭那条路上的绿灯
public Lamp close(){

flag = false;
if(correlight != null){
Lamp.valueOf(correlight).close();
}
//关闭的同时,要打开该灯的下一个灯,如果有的话
if(next !=null){
nextlight = Lamp.valueOf(next);
nextlight.oppen();
}
return nextlight;

}



}


③LampManage类的编写

该类为一个交通灯的总控制,为了达到方便 ,我们创建只有一个线程的线程池,该线程池用来控制红绿灯的交换时间,我这里初始灯是由S2N,在覆盖了接口Runnable的run方法中,要获得Lamp类中close方法返回的下一个绿灯的对象。由此就可以不断循环控制整个交通灯,

这里设置了每隔10s绿灯就切换到下一条线路上。

package cn.itcast.Traffic;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class LampManage {
//该类用来管理整个交通灯的运作
//也就是搞一个定时器,让交通灯可以互相切换

Lamp lamp ;

public LampManage(){
//先指定以那条路上的灯开始

lamp = Lamp.S2N;

ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);//设置一个定时器,也就是开启一个线程
timer.scheduleAtFixedRate(
new Runnable() {

@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(lamp+"方向的灯亮了。。。。。。。。。");
System.out.println("相对应的有6个方向可以通车了 分别是 :S2E,E2N,N2W,W2S,"+lamp.correlight+","+lamp);
lamp = lamp.close();//开启10s后执行该段代码,也就是关闭当前的交通灯开启下一个交通灯


}
},
10,
10,
TimeUnit.SECONDS);



}


}

④MainManage类的编写

该类的编写很简单,就是构造12条线路,调用交通灯的总控制类,使得可以在每条线路随机增加汽车且调动了红绿灯来控制车辆之间的来往

package cn.itcast.Traffic;
public class MainManage {


//产生十二个红绿灯
public static void main(String[] args) {
String lamp[]={"S2N","S2W","E2W","E2S","N2S","N2E","W2E","W2N","S2E","E2N","N2W","W2S"};

for(int i=0;i<lamp.length;i++){
new Road(lamp[i]);

}

//启动交通灯
new LampManage();

}


}


到此交通灯模拟就算是结束,,,看似简单,却是用到了很多的基础知识,尤其是面向对象的基础。想要学得更好,基础是必不可少的。