一、功能需求:
模拟实现十字路口的交通灯管理系统逻辑,具体需求如下:
Ø 异步随机生成按照各个路线行驶的车辆。
例如:
由南向而来去往北向的车辆 ----直行车辆
由西向而来去往南向的车辆 ----右转车辆
由东向而来去往南向的车辆 ----左转车辆
。。。
Ø 信号灯忽略黄灯,只考虑红灯和绿灯。
Ø 应考虑左转车辆控制信号灯,右转车辆不受信号灯控制。
Ø 具体信号灯控制逻辑与现实生活中普通交通灯控制逻辑相同,不考虑特殊情况下的控制逻辑。
注:南北向车辆与东西向车辆交替放行,同方向等待车辆应先放行直行车辆而后放行左转车辆。
Ø 每辆车通过路口时间为1秒(提示:可通过线程Sleep的方式模拟)。
Ø 随机生成车辆时间间隔以及红绿灯交换时间间隔自定,可以设置。
Ø 不要求实现GUI,只考虑系统逻辑实现,可通过Log方式展现程序运行结果。
--------------------------------------
个人认为,交通灯系统中应该要考虑黄灯,哪个路口的红绿灯只有红灯、绿灯没有黄灯着?
所以本文将增加此功能,将黄灯纳入需求中。
从随便生成车辆时间间隔与红绿灯交换时间间隔自定,且可设置。可以提取出,这个时间最好是可配置的,不要硬编码写在代码中。这样下次需要改时间,还需要改代码,太麻烦。而且,除了红绿灯切换间隔时间外,个人觉得像车辆通过时间间隔,随机生成车辆时间间隔及开始哪个路线方向上的灯先亮都应该是可配置的。
二、面向对象分析:
1. 分析对象有几个?
简单的用名词分析法得出共有车辆、灯、路线,为了给路线的灯进行切换红绿灯应该还有个控制器才行。就是张老师讲的“石头磨成石刀,石刀可以吹树,砍成木材,木材做成椅子”。 这里的石头与石刀之间,应该不是谁拥有谁,石头变石刀既不是石头的行为也不应该是石刀的行为。所以需要一个中间者来完成即一个石刀工厂来搞定。石刀砍树,砍树变成了木材了,所以石刀应该有个砍方法,砍需要传递一个树,砍完后变木材,所以砍方法返回应该是个木材。木材做成椅子,同样也是由一个中间者完成的。所以这句话抽象成了下面几个对象。:
Stone、StoneKnife、StoneKnifeFactory、Tree、Wood、Chair、ChairFactory。 Stone--->StoneKnife 用StoneKnifeFactory的CreateStoneKnife(Stone)完成。
Wood StoneKnife.chop(Tree)就是石刀砍树后变成木材,Wood--->Chair用ChairFactory的CreateChair(Wood)来完成。至少为什么StoneKnife中为什么不拥有Tree要在Chop方法传递一个Tree,而不是构造时直接传个Tree。应当是石刀砍树,并不只能砍一个树或指定的一堆树,它可以砍任何的树,也可以砍其它的东西比如花等。
而上面的功能需求中车辆并没有要求描述是什么车辆或车辆的信息,是小骄车还是大卡车还是什么?所以这里的车辆直接用个字符串表示,去掉这个对象。那么现在共有这几个对象: 控制器、路线、灯,等等似乎还忘记了什么,对配置信息呢,这个得应该是个工具类,用单例与静态类都可以。由于这个要读取配置,需要提取初始化一些信息,得到一个Map的映射后找配置值就简单了,所以不能直接提供静态方法获取配置值,否则每次调用此方法都进行初始化配置太浪费资源了。另个为了调用测试这个程序,最后单独弄一个客户端Cliet。
所以呢最终我们的类需要用:路线 RoadLine、灯Lamp 控制器RoadLineController、配置读取工具ConfigReader,客户测试Client 共五个类。
2. 分析对象的数据行为?
有了对象后,就需要分析下各对象有什么数据行为。灯Lamp,由于需要考虑黄灯,而且张老师讲的灯有十二种,个人并不认为洽当个人认为,灯应该是个枚举不错,但应该只有三种,红、绿、黄。至于由S->N、N->S等等,应该是路线的实例不应该是灯的,如果需要考虑复杂点,S->N画黄白线,距离多少米用摄像头要监控等等需求时,显然S->N、N->S等,都不是灯的实例对象,它是路线方向的对象,那么也就自然需要改动另一个地方了,路线应该也不是个普通的类,它应该是个枚举,一般也只有十二个方向的路线,不应该有无数个未确定上限个方向的路线。
2.1 如何分析出十二个方向的灯的呢,这些细节是如何想到了着,画面。
对,张老师讲的很对,要画面。当一个业务复杂了点,就不应该冲上去就写程序,没有思路是写不好的。不妨先画图研究下,生活中看到的红绿灯,南到北与北到南就是一组红绿灯嘛,两者变化应该是完全同步的嘛。那么具体的图是什么呢。
像南S->东E、东E->北N、北N->西W、西W->南S,是不需要有红绿灯着,可以认为它们永远都是绿灯呢。现实上是一般路线大多也没有设置右转向灯,右转是注意直行让着直行就是了。而另外的八条路线经分析,其实是四组路线呢,每组路线中的灯应该是完全同步的。如南到北S->N、北到南N->S是一组两者变化应该是同步的。具体的四组如下:
* 组一:S->N、N->S、* 组二:S->W、N->E、* 组三:E->W、W->E、* 组四:E->S、W->N
这样很顺利的将复杂的问题,十二路线灯的变化简化成了只要关注四个方向路线上的灯变化就可以了分析是S->N、S->W、E->W、E>S即可了。
拥有数据分析:
2.1.1RoadLine分析:
2.1.1.1路线方向RoadLine显示,需要在上面有车辆加入与通过跳出。所以应该只需要个一个集合即可,为了简单起见,直接拿个ArrayList来加入add装String车辆,与减跑出remove车辆。
2.1.1.2由于上面的四组,像S->N拥有相反方向路线,所以需要弄个opposite来描述当前路线中相反的路线是什么。
2.1.1.3而路线中灯的切换,由S->N换成S->W等,可以看出显示还需要弄个此路线灯变成红灯后下个路线是什么,所以需要有个nextRoadLine来描述下一路线。
2.1.1.4由于路线中车辆的跑出移走等都是需要判断,当前路线的灯是否不是红灯,所以必须拥有一个当前的灯着实例lamp。
2.1.1.5 这些众多的路线实例,应该需要提供一个变切换的方法,由此灯变成另一灯或由此路线变成另一路线,而这些都是与RoadLine拥有变量实例有关系,所以changRoadLineLamp方法肯定是需要RoadLine去提供了。
2.1.1.5 而控制器需要看当前路线方向上的灯,是什么灯此灯配置的时间停留多长来,决定等待这些秒数后,再变切换路线方向灯。所以RoadLine方法还需要用getSecond方法来提供,停留多少灯去变化。
2.1.2 灯Lamp分析:
2.1.2.1 Lamp 灯应该是秒数,即多少灯是红灯着,红灯亮多少秒。
2.1.2.2 Lamp灯共有三个实现红、绿、黄这个实例,每次如何变化灯,从红变成绿还是黄,应该是Lamp提供,即nextLamp()方法。
2.1.3 控制器与配置读取器、客户端的分析
2.1.3.1 控制器 需要指定当前的路线,从配置文件中读出来的初始路线是什么方向路线。由此路线开始过lamp停留秒后,再切换路线方向灯。所以需要拥有一路线方向currentRoadLine。甚至其它的,就是有了此路线方法后,那就隔灯的停留秒就一直切换呗。
2.1.3.2 配置读取器 前面已经分析到这里要始初化一些资源信息,所以用单例实例对象比较好,一次性在构造方法中作为初始化的工作。后面拿到Map后,找相应配置关键字对应的值就可以了。
2.1.3.3 客户端,没什么说的,加载始初化路线方向与控制器就可以了。
具体实现代码如下:
RoadLine类代码:
package com.isoftstone.interview.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; /** * 个人认为,还有一种更为有效率的交通灯应该归为两组灯较与合适,这与实现比较相符。 * 直行的与左拐弯的灯,应该是一致的,当然与各自的相反的灯也是一致的。即 * 组一:S->N、S->W、N->S、N->E * 组二:E->W、E->S、W->E、W->N * * 这里以比较复杂的,四组交通灯为例。四组上交通灯分别是 * 组一:S->N、N->S * 组二:S->W、N->E * 组三:E->W、W->E * 组四:E->S、W->N * * 二种方式的共同之处,在于右转变的车一般在避让直接车时即可。它永远都是绿灯着 * @author chen * */ public enum RoadLine { /* 需要控制的灯 */ S2N("N2S","S2W",Lamp.RED),N2S(null,null,Lamp.RED), S2W("N2E","E2W",Lamp.RED),N2E(null,null,Lamp.RED), E2W("W2E","E2S",Lamp.RED),W2E(null,null,Lamp.RED), E2S("W2N","S2N",Lamp.RED),W2N(null,null,Lamp.RED), /* 有下面四条路线上的灯,不需要控制,永远是绿色直走就是了,注意避让直行车即可。 */ S2E(null,null,Lamp.GREEN),E2N(null,null,Lamp.GREEN),N2W(null,null,Lamp.GREEN),W2S(null,null,Lamp.GREEN); private String opposite; //与此路线相反的路线,相反路线的状态灯是随此路线的灯同步变化着。 private Lamp lamp ; //路线中的灯 private String nextRoad ; //当前路线红灯时,轮到哪个路线上亮绿灯 private List<String> vehicles ; /** * 路线构造方法,主要用于,给每个线路上每隔一个随机时间内进行添加车辆,和每隔一相对固定的时间内减少车辆 * 当然,减少车辆时,必须满足此路线上是有车辆且路线上的灯非红灯 */ private RoadLine(String opposite, String nextRoad , Lamp lamp){ this.opposite = opposite; this.nextRoad = nextRoad; this.lamp = lamp; vehicles = new ArrayList<String>(); //用JDK1.5中的 线程玩法。 ExecutorService es = Executors.newSingleThreadExecutor(); es.execute(new Runnable(){ public void run(){ int add_vehicles_time = Integer.valueOf(ConfigReader.getInstance().readConfig("add_vehicles_time")); Random rd = new Random(); for(int i=0;i<1000;i++){ try { //在add_vehicles_time秒内,随便加一个车辆进去 Thread.sleep((rd.nextInt(add_vehicles_time)+1)*1000); } catch (InterruptedException e) { e.printStackTrace(); } vehicles.add(RoadLine.this.name()+"__vehicles__"+i); } } }); ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor(); //在初始init_remove_vehicles_time秒之后,每个remove_vehicles_period_time秒检查下,是否当前的灯不是红灯且路线中有车辆,满足条件则将车辆通过。 ses.scheduleAtFixedRate( new Runnable(){ public void run(){ if(RoadLine.this.lamp != Lamp.RED&&vehicles.size()>0){ String removeVehicle = vehicles.remove(0); System.out.println(removeVehicle+" is travelling"); } } }, Long.valueOf(ConfigReader.getInstance().readConfig("init_remove_vehicles_time")), Long.valueOf(ConfigReader.getInstance().readConfig("remove_vehicles_period_time")), TimeUnit.SECONDS); } /** * 切换路线中的灯,并返回线路。 * @return 返回路线切换灯后的路线。若当前路线没有到红灯则还是自身。若已经是红灯了,则返回下个线路。 */ public RoadLine changeLamp(){ //得到路线上灯的下一个灯是什么的灯 lamp = lamp.nextLamp(); System.out.println("-------------------------changeLamp-------------------------"); //打印路线上灯的状态 System.out.println("Lamp of "+this.name()+" RoadLine is "+lamp.name()); if(null != opposite){ //如果存在相反路线方向时,则将此相反路线上的灯也置成下个灯 //则将此相反路线上的灯也置成下个灯,注意这里存在个问题就是相反方向的灯,下次取值时还是RED。所以应该修下。 //ComplexRoadLine.valueOf(opposite).lamp.nextLamp(); //要将相反路线的灯,转置下个灯时,保存起来,否则每次都是RED了。 RoadLine oppositeRoadLine = RoadLine.valueOf(opposite); oppositeRoadLine.lamp = oppositeRoadLine.lamp.nextLamp(); //打印相反路线上灯的状态 System.out.println("Lamp of "+oppositeRoadLine.name()+" RoadLine is "+oppositeRoadLine.lamp.name()); } RoadLine retVal = this ; if(Lamp.RED == lamp){ //若已经是红灯了,则将下个路线的灯也切换下。 RoadLine.valueOf(nextRoad).changeLamp(); retVal = RoadLine.valueOf(nextRoad); //此是的要返回的retVal必须是在调用此方法之后,这样回到初始调用的位置this的nextRaod才是真的下一路线 } //若本次路线的灯已到红灯时,那么就返回下个路线,否则还是本路线自省 return retVal; } /** * 当前,此路线中的灯过几秒后,要切换的。 * @return */ public int getLampSecond() { return lamp.getSecond(); } }
Lamp枚举代码:
package com.isoftstone.interview.traffic; /** * 交通灯,红绿黄灯,只可能用三个灯,用枚举。且每个灯都有自己的停留时间及切换时,下个灯是什么的方法 * @author chen * */ public enum Lamp { //红灯,构造时先读取红灯的时间.且需要实现红灯下一个灯是什么 RED(Integer.valueOf(ConfigReader.getInstance().readConfig("red_time"))){ public Lamp nextLamp(){ return GREEN; } }, //绿灯,构造时先读取绿灯的时间.且需要实现绿灯下一个灯是什么 GREEN(Integer.valueOf(ConfigReader.getInstance().readConfig("green_time"))){ public Lamp nextLamp(){ return YELLOW; } }, //黄灯,构造时先读取黄灯的时间.且需要实现黄灯下一个灯是什么 YELLOW(Integer.valueOf(ConfigReader.getInstance().readConfig("yellow_time"))){ public Lamp nextLamp(){ return RED; } }; private int second ; //灯实例停留的时间 //此灯实现下个灯是什么,具体实现与当前的实例有关系,为了减少if-else语句,直接声明成抽象方法,由各实例对象自定去实现。 public abstract Lamp nextLamp(); private Lamp(int second){ this.second = second; } public int getSecond() { return second; } public void setSecond(int second) { this.second = second; } }
控制器:
package com.isoftstone.interview.traffic; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * 控制器,控制着当前路线灯,什么时间去切换路线灯改变各路线方向灯的状态 * @author chen * */ public class RoadLineController { private RoadLine currentRL ; //保存当前的路线 public RoadLineController(){ //初始,从配置文件中读取,哪一个路线的灯先被切换。 currentRL = RoadLine.valueOf(ConfigReader.getInstance().readConfig("init_start_roadline")); ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor(); ses.scheduleAtFixedRate( new Runnable(){ public void run(){ try { //当前路线的灯,亮currentRL.getLampSecond()秒后,则将路线的灯切换一次。 Thread.sleep(currentRL.getLampSecond()*1000); } catch (InterruptedException e) { e.printStackTrace(); } currentRL = currentRL.changeLamp(); } }, Long.valueOf(ConfigReader.getInstance().readConfig("init_start_time")), Long.valueOf(ConfigReader.getInstance().readConfig("change_roadline_time")), TimeUnit.MILLISECONDS); } }
配置读取器
package com.isoftstone.interview.traffic; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** * 单例模式,用于读取交通灯中,相应的与程序配置相关的信息。 * 如红灯亮几秒后,转成绿灯,而绿灯亮几秒切换成黄灯,而黄灯亮几秒切换成红灯等等信息。 * * @author chen * */ public class ConfigReader { private Properties props ; private static final ConfigReader cr = new ConfigReader(); /** * 用单列模式,保证只有一份实例,以免创建过多实例对象,导致内存资源的不必要的浪费。 * @return */ public static ConfigReader getInstance(){ return cr; } /** * 根据提供的配置信息的Key,获取配置信息的值。 * @param key 配置信息的Key * @return 返回配置信息中找到的值 */ public String readConfig(String key){ return props.getProperty(key); } /** * 需要在构造方法中,初始化一些资料信息 */ private ConfigReader(){ props = new Properties(); InputStream is = ConfigReader.class.getResourceAsStream("traffic_conf.properties"); try { props.load(is); } catch (IOException e) { e.printStackTrace(); } } }
traffic_conf.propertiestraffic_conf.properties 配置文件(把这忘记了,无语...):
#交通灯系统配置文件 #################################红绿黄灯配置部分################################# #红灯是亮50秒后,转成绿灯。 red_time=3 #绿灯亮30秒后,转成黄灯 green_time=2 #黄灯亮5秒后,转成红灯 yellow_time=1 #################################路线上加车辆及减车辆的配置部分################################# #初始化是1秒后,开始检查灯是否为绿灯,是绿灯则通过 init_remove_vehicles_time=1 #每隔1秒后,再检查次,是否为绿灯,是绿灯则通过 remove_vehicles_period_time=1 #5秒之内,随机出现一个车辆被加入到相应的路线中。 add_vehicles_time=5 #################################控制器的配置部分################################# #程序开始时,先让哪条路线中的红绿亮变成 绿灯。 init_start_roadline=S2N #程序开始执行时,即控制器开始执行等待的时间以毫秒计 init_start_time=1 #每次控制器切换一个路线的红绿灯时,隔开change_roadline_time毫秒+当前路线灯亮的秒数后,切换路线的灯为下一个灯 change_roadline_time=1 #中文乱码无法存储可以 右键traffic_conf.properties--->properties--->text file encoding --->other 调UTF-8
客户端代码:
package com.isoftstone.interview.traffic; public class Client { public static void main(String[] args){ RoadLine.values(); new RoadLineController(); /* * 下面是运行结果,与设想完全符合: * E2N__vehicles__0 is travelling S2E__vehicles__0 is travelling -------------------------changeLamp------------------------- Lamp of S2N RoadLine is GREEN Lamp of N2S RoadLine is GREEN S2N__vehicles__0 is travelling W2S__vehicles__0 is travelling N2S__vehicles__0 is travelling N2W__vehicles__0 is travelling W2S__vehicles__1 is travelling -------------------------changeLamp------------------------- Lamp of S2N RoadLine is YELLOW Lamp of N2S RoadLine is YELLOW S2N__vehicles__1 is travelling S2E__vehicles__1 is travelling N2W__vehicles__1 is travelling E2N__vehicles__1 is travelling -------------------------changeLamp------------------------- Lamp of S2N RoadLine is RED Lamp of N2S RoadLine is RED -------------------------changeLamp------------------------- Lamp of S2W RoadLine is GREEN Lamp of N2E RoadLine is GREEN S2W__vehicles__0 is travelling N2E__vehicles__0 is travelling S2E__vehicles__2 is travelling N2E__vehicles__1 is travelling -------------------------changeLamp------------------------- Lamp of S2W RoadLine is YELLOW Lamp of N2E RoadLine is YELLOW S2W__vehicles__1 is travelling W2S__vehicles__2 is travelling -------------------------changeLamp------------------------- Lamp of S2W RoadLine is RED Lamp of N2E RoadLine is RED -------------------------changeLamp------------------------- Lamp of E2W RoadLine is GREEN Lamp of W2E RoadLine is GREEN E2W__vehicles__0 is travelling W2E__vehicles__0 is travelling S2E__vehicles__3 is travelling N2W__vehicles__2 is travelling E2N__vehicles__2 is travelling E2W__vehicles__1 is travelling W2E__vehicles__1 is travelling -------------------------changeLamp------------------------- Lamp of E2W RoadLine is YELLOW Lamp of W2E RoadLine is YELLOW W2E__vehicles__2 is travelling S2E__vehicles__4 is travelling E2N__vehicles__3 is travelling -------------------------changeLamp------------------------- Lamp of E2W RoadLine is RED Lamp of W2E RoadLine is RED -------------------------changeLamp------------------------- Lamp of E2S RoadLine is GREEN Lamp of W2N RoadLine is GREEN E2S__vehicles__0 is travelling W2N__vehicles__0 is travelling E2S__vehicles__1 is travelling W2N__vehicles__1 is travelling N2W__vehicles__3 is travelling W2S__vehicles__3 is travelling -------------------------changeLamp------------------------- Lamp of E2S RoadLine is YELLOW Lamp of W2N RoadLine is YELLOW E2S__vehicles__2 is travelling W2N__vehicles__2 is travelling S2E__vehicles__5 is travelling E2N__vehicles__4 is travelling -------------------------changeLamp------------------------- Lamp of E2S RoadLine is RED Lamp of W2N RoadLine is RED -------------------------changeLamp------------------------- Lamp of S2N RoadLine is GREEN Lamp of N2S RoadLine is GREEN S2N__vehicles__2 is travelling */ } }