做一个模拟的交通灯管理系统
一,功能需求分析
通过交通灯控制系统控制路口的红绿灯的状态以疏通路口上各个走向的车辆
涉及四类事物:交通灯,交通灯控制系统,交通线路 ,车辆
功能描述:
1,每个走向的红绿灯都有红绿状态,并且能够提供切换他们的状态的功能
2, 每个走向的路具有判断该走向的红绿灯的当前状态的功能,并根据判断出状态为
绿时运行该走向的车通行,红灯时让车辆等待的功能,
3, 用一个交通灯控制系统来控制各走向的红绿灯,
思路分析:
1,一共12个走向,其中只有8个走向是受红绿灯控制的,4个右拐走向都不是红绿灯的控制
但是为了方便描述这4个走向也假设有红绿灯,保持绿灯状态.
2,交通灯忽略黄灯,只考虑红绿灯和绿灯
3,交通灯状态的需要有序循环切换,指定的时间间隔切换
4,每个走向的道路上来车时间间隔具有随机性,并且各个走向的来车情况不受别的走向的
影响,在某走向的灯为绿时,按1秒的时间间隔让车辆通行,同时按1秒的间隔检查该走向的
红绿灯状态
二,面向对象分析和设计
一共12个走向:
直走方向--->南向北,北向南,东向西,西向东;
左拐方向--->南向西,北向东,东向南,西向北;
右拐方向--->南向东,北向西,东向北,西向南;
其中只有直走和左拐方向需要红绿灯控制,而且每两个相对方向同时受同一个红绿灯状态的
控制,故将直走左拐方向两两相对方向分组如下:
南向北,北向南,
南向西,北向东,
东向西,西向东,
东向南,西向北;
控制系统只需控制每组的一个灯则另一个灯状态跟随改变,由一个组的灯绿切换为红灯的同时
将下一组由红灯切换为绿灯,依次切换四次然后循环,每组受控制和四组的切换顺序如下:
南向北,南向西,东向西,东向南;
右拐方向不受红绿灯控制,但可以模拟为红绿灯的常绿状态
将涉及到问题的四个事物抽象为三个类:
交通灯(TrafficLight),路(Road),交通灯控制系统(ControlSystem).
1)路(Road)
设计Road表示线路,12个走向用12个Road对象表示,根据面向对象的设计经验:
谁拥有数据那么谁就提供操作该数据的功能,故将车辆归为Road的数据
每条线路上随机的时间间隔增加车辆,在每条线路上的红绿灯为绿时按1秒的时间
间隔减少一辆车来代表通过路口,同时按1秒的间隔检查该走向的红绿灯的状态,故
用一个集合来增加和减少车辆
Road有如下成员变量,
name 代表每个Road实例代表的走向的名称
vehicles 代表每个走向的车辆集合,有该集合负责车辆的开来和开走
Road的每个对象代表的12条并发增减对应走向的车辆数量以及检测该走向的红绿灯状态
这些功能不受相互之间的影响 ,所以要求由产生代表每个走向的对象的构造方法来启动各
自的线程,并且启动一个定时器每间隔1秒检测该走向的红绿灯在状态,根据状态来控制集合
车辆的增删
代码:
package com.itheima.trafficlight; 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> vechicles = new ArrayList<String>(); /*名称属性代表走向*/ private String name =null; /*构造方法通过传入的名称产生该名称代表的走向的Road对象*/ public Road(String name){ this.name = name; ExecutorService pool = Executors.newSingleThreadExecutor(); /* * Executors是一个工具类,主要用来创建和管理线程 * newSingleThreadExecutor() * -----创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程 * 返回类型是ExecutorService 接口的实现类提供的方法 * shutdown()并不是终止线程的运行,而是禁止在这个Executor中添加新的任务 * execute(Runnable command) 将一个Runnable的实现类的实例作为参数传入,执行相应线程。 * execute是ExecutorService的父接口Executor的唯一的方法 ,根据多态用ExecutorService * 来指向实现类对象,所以调用execute的时候就是调用对应指向的实现类中的execute方法 * */ pool.execute(new Runnable(){//用内部类直接实现Runnable并将其实例传给execute来执行 public void run(){ for(int i=1;i<1000;i++){ try { //按随机的时间间隔在该线路上增加一辆车 Thread.sleep((new Random().nextInt(10) + 1) * 1000); } catch (InterruptedException e) { e.printStackTrace(); } vechicles.add(Road.this.name + "_" + i); } } }); /*每隔一秒检查该走向的灯是否为绿,是则放行一辆车*/ ScheduledExecutorService timer = Executors.newScheduledThreadPool(1); /* * ScheduledExecutorService也是ExecutorService的实现类 ,可安排在给定的延迟后运行或定期执行的命令 * 其方法scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) * 创建并执行一个在给定初始延迟后首次启用的定时操作,后续操作具有给定的周期;也就是将在 initialDelay 后开始执行 * 然后在 initialDelay+period 后执行,接着在 initialDelay + 2 * period 后执行,依此类推。 * 参数: command - 要执行的任务 initialDelay - 首次执行的延迟时间 period - 连续执行之间的周期 unit - initialDelay 和 period 参数的时间单位 * */ timer.scheduleAtFixedRate( new Runnable(){ public void run(){ if(vechicles.size()>0){ TrafficLight currentLight = TrafficLight.valueOf(Road.this.name); boolean green = currentLight.isGreen(); if(green){ System.out.println(vechicles.remove(0) +TrafficLight.getNames()[currentLight.ordinal()]); } } } }, 1, 1, TimeUnit.SECONDS); } }
2) 交通灯(TrafficLight)
设计TrafficLight枚举类,每个走向的红绿灯分别用一个枚举元素来表示 ,12个走向对应12个
TrafficLight枚举元素,灯名表示走向,根据上面分析分组如下:
受控制系统控制红绿灯:S2N,S2W,E2W,E2S,-->每隔10秒自左向右依次由红变绿
受同一组控制的红绿灯:N2S,W2S,W2E,S2E,-->随着上面一组对应方向的灯变化
保持常绿状态的红绿灯:S2E,N2W,E2N,W2S;
TrafficLight包含三个成员变量,分别代表:
boolean green 当前走向的灯是否为绿,绿则值为true,红则为false
String oppositeLight 与当前走向相对的走向的灯,
String nextLight当前走向的灯由绿变红时下一个由红变绿的灯,
用构造方法来初始化每个红绿灯的三个属性
Lamp包含两个成员方法:
isGreen 负责将红绿灯设为绿灯,
isRed 负责将红绿灯设为红灯并且让下一组相对走向的绿灯亮
因为枚举元素必须在定义之后引用,所以无法在构造方法中彼此相互引用,
所以,相对方向和下一个方向的灯用字符串形式表示,在需要转换为枚举元素
时用枚举提供的静态方法valueOf(String name)来得到对应名称的枚举元素引用
除了S2N、S2W、E2W、E2N这四个方向上的Lamp对象之外,其他方向上的TrafficLight对象
的nextLight和oppositeLight属性设置为null即可,并且S2N、S2W、E2W、E2N这四个方向
上的TrafficLight对象的nextLight和oppositeLight属性必须设置为null,以
防止isGreen和isRed进入死循环。
代码 :
package com.itheima.trafficlight; public enum TrafficLight { /*下面的四个是受控制系统控制每隔10秒自左向右依次由红变绿并循环的枚举元素*/ S2N("N2S","S2W",false),S2W("N2E","E2W",false),E2W("W2E","E2S",false),E2S("W2N","S2N",false), /*下面元素受上面的元素的控制,随着上面一组相对方向的灯变化,它们不用控制别的灯的状态所以,前两个参数为null*/ 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); /*当前灯是否为绿,是为true*/ private boolean green; /*与当前灯状态相同的红绿灯*/ private String oppositeLight; /*当前灯变红时下一个变绿的灯*/ private String nextLight; /*走向名称列表*/ private static String[] names = {"南向北直走","南向西左拐弯","东向西直走","东向南左拐弯", "北向南直走","北向东左拐弯","西向东直走","西向北左拐弯", "南向东右拐弯","东向北右拐弯","北向西右拐弯","西向南右拐弯"}; /*通过构造方法,在得到每个走向的红绿灯时初始化它们的三个属性*/ private TrafficLight(String oppositeLight,String nextLight,boolean green){ this.oppositeLight = oppositeLight; this.nextLight = nextLight; this.green = green; } /** * 可以获取当前灯的红绿状态 */ public boolean isGreen(){ return green; } public static String[] getNames(){ return names; } /** * 当前受控制系统控制的灯由红变绿,并将相对方向的灯也设置为绿灯 */ public void setGreen(){ green = true; if(oppositeLight != null){ //将相对方向的灯同时设为绿灯 TrafficLight.valueOf(oppositeLight).setGreen(); } System.out.println(names[this.ordinal()]+ "绿灯亮啦,该走向的车辆可以通行了!!!"); } /** * 当前受控制系统控制的灯由绿变红,同时将相对方向的灯也由绿变红 , * 并按顺序让下一个受控制系统控制的灯由红变绿 * @return 下一个要变绿的灯 */ public TrafficLight setRed(){ System.out.println(names[this.ordinal()]+"要变红灯啦"); this.green = false; if(oppositeLight != null){ TrafficLight.valueOf(oppositeLight).setRed(); } TrafficLight nextGreenLight = null ; if(nextLight != null){ nextGreenLight = TrafficLight.valueOf(nextLight); nextGreenLight.setGreen(); } return nextGreenLight; } }
3)交通灯控制系统(ControlSystem)
将按每10秒的时间间隔依次切换四个红绿灯(S2N,S2W,E2W,E2S)的控制系统ControlSystem
设计为单例 , 因为整个系统中只能有一套交通灯控制系统。
ControlSystem构造方法设定第一个(S2N)为绿灯。
ControlSystem对象的start方法中将当前灯变绿,然后启动一个定时器,每隔10秒将当前灯
变红和将下一个灯变绿。
代码:
package com.itheima.trafficlight; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class ControlSystem { private TrafficLight currentLight; /*控制系统构造方法*/ public ControlSystem(){ /*控制系统初始化时,先让南向北走的红绿灯变绿*/ currentLight = TrafficLight.S2N; currentLight.setGreen(); /*每隔10秒将当前绿灯变为红灯,并让下一个方向的灯变绿*/ ScheduledExecutorService timer = Executors.newScheduledThreadPool(1); timer.scheduleAtFixedRate(new Runnable(){ public void run(){ currentLight = currentLight.setRed(); } }, 10, 10, TimeUnit.SECONDS); } }
4)MainThread
主线程需要创建代表12条走向的Road对象,并
代码 :
package com.itheima.trafficlight; public class MainThread { /** * @param args */ public static void main(String[] args) { /*主线程需要创建代表12条走向的Road对象,并分别启动一个线程*/ String [] directionNames = new String[]{ "S2N","S2W","E2W","E2S","N2S","N2E","W2E","W2N","S2E","E2N","N2W","W2S" }; for(int i=0;i<directionNames.length;i++){ new Road(directionNames[i]); } /*产生整个交通灯系统*/ new ControlSystem(); } }
执行结果:
北向南直走绿灯亮啦,该走向的车辆可以通行了!!!
南向北直走绿灯亮啦,该走向的车辆可以通行了!!!
S2E_1南向东右拐弯
W2S_1西向南右拐弯
S2N_1南向北直走
E2N_1东向北右拐弯
S2E_2南向东右拐弯
N2W_1北向西右拐弯
S2N_2南向北直走
N2S_1北向南直走
E2N_2东向北右拐弯
南向北直走要变红灯啦
北向南直走要变红灯啦
北向东左拐弯绿灯亮啦,该走向的车辆可以通行了!!!
南向西左拐弯绿灯亮啦,该走向的车辆可以通行了!!!
S2W_1南向西左拐弯
N2E_1北向东左拐弯
E2N_3东向北右拐弯
S2W_2南向西左拐弯
N2E_2北向东左拐弯
N2W_2北向西右拐弯
S2E_3南向东右拐弯
N2W_3北向西右拐弯
W2S_2西向南右拐弯
S2W_3南向西左拐弯
S2E_4南向东右拐弯
N2E_3北向东左拐弯
E2N_4东向北右拐弯
南向西左拐弯要变红灯啦
北向东左拐弯要变红灯啦
西向东直走绿灯亮啦,该走向的车辆可以通行了!!!
东向西直走绿灯亮啦,该走向的车辆可以通行了!!!
E2W_1东向西直走
W2E_1西向东直走
E2W_2东向西直走
W2E_2西向东直走
E2W_3东向西直走
W2E_3西向东直走
N2W_4北向西右拐弯
E2W_4东向西直走
W2E_4西向东直走
W2S_3西向南右拐弯
W2E_5西向东直走
S2E_5南向东右拐弯
W2E_6西向东直走
S2E_6南向东右拐弯
E2W_5东向西直走
E2N_5东向北右拐弯
N2W_5北向西右拐弯
W2S_4西向南右拐弯
东向西直走要变红灯啦
西向东直走要变红灯啦
西向北左拐弯绿灯亮啦,该走向的车辆可以通行了!!!
东向南左拐弯绿灯亮啦,该走向的车辆可以通行了!!!
E2S_1东向南左拐弯