專案——交通燈管理系統
交通燈管理系統
一.交通燈管理專案的需求
模擬實現十字路口的交通燈管理系統邏輯,具體要求如下:
1. 非同步隨機生成按照各個路線行駛的車輛。
例如:
由北向駛往南向的車輛----直行
由西向駛往南向的車輛----右轉
由東向駛往南向的車輛----左轉
2. 訊號燈忽略黃燈,只考慮紅燈和綠燈。
3. 應考慮左轉車輛受訊號燈控制,右轉車輛不受訊號燈控制。
4. 具體訊號燈控制邏輯與現實生活中普通交通燈控制邏輯相同,不考慮特殊情況下的控制邏輯:
南北車輛與東西車輛交替放行,同方向車輛等待應先放行直行車輛,後放行左轉車輛。
5. 每輛車通過路口的時間為1秒(提示:可通過執行緒sleep的方式模擬)。
6. 隨機生成車輛時間間隔以及紅綠燈交換時間間隔自定,可以設定。
7. 不要求實現GUI,只考慮系統邏輯實現,可通過log方式展現程式執行結果。
二.需求分析
總共有12條路線。為了統一程式設計模型,可以假設每條路線都有一個紅綠燈對其進行控制,右轉彎的4條路線的控制燈可以假設為常綠狀態。其他8條路線兩兩成對,可以歸為4組。所以,程式只需要考慮圖中標註了編號的4條路線的控制燈的切換順序,這4條路線的控制燈跟隨這4條路線切換,不必額外考慮。
三.面向物件的分析與設計
1.每條路線上都會出現多輛車,路線上要隨機增加新的車,在綠燈期間還要每秒鐘減少一輛車。
1)設計一個Road類來表示路線,每個Road物件代表一條路線,總共12條路線
即系統中總共產生12個Road例項物件。
2)每條線路上隨機增加新的車輛,增加到一個集合中儲存。
3)每條線路每隔1秒都會檢查控制本路線的燈是否為綠。若是綠燈,將本路線儲存
車的集合中的第一輛車移除,即表示車穿過了路口。
2.每條路線每隔1秒都會檢查控制本路線的燈是否為綠。一個燈由綠變紅時,應該將下一個方向的燈變綠。
1) 設計一個Lamp類來表示一個交通燈,每個交通燈都維護一個狀態,亮(綠)或不亮(紅),每個交通燈要有變亮
和變黑的方法,並且能返回自己亮黑狀態。
2) 總共有12條路線,系統要產生12個交通燈。右轉彎的路線本來不受燈控制,但是為了讓程式有統一的處理方式,
所以假設四個右轉彎的燈,只是需要將這些燈設定為常亮狀態,永遠都不要變黑。
3) 除了右轉彎方向的其他8條路線的燈,他們是兩兩成對的,可以歸為四組,所以,在程式設計時,只要從這4組燈中各
取出一個燈,使這4個燈輪流變亮,與這4個燈方向對應的燈隨之一同變化,因此Lamp類中要有一個變數來記住自
己相反方向的燈。在一個Lamp物件的變亮和變黑方法中,將對應方向的燈也變亮和變黑,每個燈變黑時,都伴隨
著下一個燈的變亮,Lamp類中還需要一個變數來記住自己的下一個燈。
4) 無論在程式的什麼地方去獲得某個方向的燈時,每次獲得的都是同一個例項物件,所以Lamp類用列舉來做顯然有
很大的方便性,永遠都只有代表12個方向的燈的例項物件。
四.程式碼實現
1.交通燈的Lamp類的程式碼
1)系統中有12個方向上的燈,在程式的其他地方要根據燈的名稱就可以獲得對應的燈的例項物件,綜合這些因素,將Lamp類用java5中的列舉形式定義更為簡單。
2)每個Lamp物件中的亮黑狀態用lighted變量表示,選用S2N、S2W、E2W、E2N這四個方向上的Lamp物件依次輪詢變亮,Lamp物件中還要有一個oppositeLampName變數來表示它們相反方向的燈,再用一個nextLampName變數來表示此燈變亮後的下一個變亮的燈。這三個變數用構造方法的形式進行賦值,因為列舉元素必須在定義之後引用,所以無法再構造方法中彼此相互引用,所以,相反方向和下一個方向的燈用字串形式表示。
3)增加讓Lamp變亮和變黑的方法:light和blackOut,對於S2N、S2W、E2W、E2N這四個方向上的Lamp物件,這兩個方法內部要讓相反方向的燈隨之變亮和變黑,blackOut方法還要讓下一個燈變亮。
4)除了S2N、S2W、E2W、E2N這四個方向上的Lamp物件之外,其他方向上的Lamp物件的nextLampName和oppositeLampName屬性設定為null即可,並且S2N、S2W、E2W、E2N這四個方向上的Lamp物件的nextLampName和oppositeLampName屬性必須設定為null,以便防止light和blackOut進入死迴圈。
<span style="font-size:18px;">//交通燈的Lamp類的程式碼
public enum Lamp {
S2N("N2S","S2W",false),S2W("N2E","E2W",false),E2W("W2E","E2S",false),E2S("W2N","S2N",false),//列舉先定義再使用。不能在定義N2S之前呼叫
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 boolean lighted;
private String opposite;
private String next;
private Lamp(){}
private Lamp(String opposite,String next,boolean lighted){ //接收引數:對應的燈,下一個燈,當前燈的亮黑狀態
this.opposite = opposite;
this.next = next;
this.lighted = lighted;
}
public boolean isLighted() { //獲取燈的亮黑狀態
return lighted;
}
public void light() {
this.lighted = true;
if(opposite!=null){ //健壯性判斷,防止死迴圈,東對應的燈是西,西對應的燈沒有。
Lamp.valueOf(opposite).light(); //根據物件名稱返回列舉。一個燈亮了,對應的燈也跟著亮。
}
System.out.println(name() + " lamp is green,下面總共應該有6個方向能看到汽車穿過!"); //四個右轉彎,;一對燈亮
}
public Lamp blackOut(){
this.lighted = false;
if(opposite!=null){
Lamp.valueOf(opposite).blackOut();
}
Lamp nextLamp = null; //記住下一個變綠的燈
if(next!=null){
nextLamp = Lamp.valueOf(next);
System.out.println("綠燈從" + name() + "---->切換為" + next);
nextLamp.light(); //變紅燈的同時,下一個燈變綠。
}
return nextLamp; //返回即將變綠的燈
}
}
</span>
2.Road類的程式碼
1)每個Road物件都有一個name成員變數來代表方向,有一個vehicles(交通工具)成員變數來代表方向上的車輛。
2)在Road物件的構造方法中啟動一個執行緒每隔一個隨機的時間向vehicles集合中增加一輛車(用一個“路線名_id”
形式的字串進行表示)。
3)在Road物件的構造方法中啟動一個定時器,每隔一秒檢查該方向上的燈是否為綠,是則列印車輛集合和將集合中的第一輛車移除掉。使用scheduleAtFixedRate方法。
<span style="font-size:18px;">import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
//Road類
public class Road {
List<String> vehicles = new ArrayList<String>(); //每條路線的車輛,裝入List集合
private String name = null;
public Road(String name) { //每條路線都要命名
this.name = name;
//模擬汽車上路
ExecutorService pool = Executors.newSingleThreadExecutor(); //建立單獨的執行緒,不停的往這個路線增加車輛
pool.execute(new Runnable() { //呼叫執行緒池中的執行器,執行空閒的執行緒
public void run() {
try {
Thread.sleep(((long)(Math.random()*10)+1)*1000); //1~10秒內隨機增加車輛
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 1000; i++) {
vehicles.add(Road.this.name + "_" + i); //外部類名.this.外部類成員變數名 訪問外部類成員變數,或者final修飾
}
}
});
//定時器,模擬汽車汽車穿過路口
ScheduledExecutorService timer = Executors.newScheduledThreadPool(1); //建立一個定時器
timer.scheduleAtFixedRate( //固定頻率的定時器
new Runnable(){
public void run() {
if(vehicles.size()>0){
boolean lighted = Lamp.valueOf(Road.this.name).isLighted();//獲取燈的狀態
if(lighted){
System.out.println(vehicles.remove(0) + " is travling ");
}
}
}},
1, //延遲1秒後開始執行任務
1, //每隔一秒執行一次
TimeUnit.SECONDS);
}
}
</span>
3.LampController交通燈控制器
1)整個系統中只能有一套交通燈控制系統,所以,LampController類最好是設計成單例。
2)LampController構造方法中要設定第一個為綠的燈。
3)LampController物件的start方法中將當前燈變綠,然後啟動一個定時器,每隔10秒將當前燈變紅和將下一個燈變綠。
<span style="font-size:18px;">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(); //點亮
//每隔10秒鐘,變紅,下一個燈變綠.建立定時器執行緒池
ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);
timer.scheduleAtFixedRate(
new Runnable() {
public void run() {
currentLamp = currentLamp.blackOut();//燈變黑同時指向下一個變綠的燈,blackOut()返回下一個變綠的燈。
}
},
2, //隔10秒執行第一次
2, //每隔10秒執行一次
TimeUnit.SECONDS);
}
}
</span>
4.MainClass類
1)用for迴圈創建出代表12條路線的物件。
2)建立紅綠燈控制系統物件,啟動系統
/*
* 12個燈:
* S2N","S2W","E2W","E2S","N2S","N2E","W2E","W2N","S2E","E2N","N2W","W2S
*/
public class MainClass {
public static void main(String[] args) {
String[] derections = new String[]{
"S2N","S2W","E2W","E2S","N2S","N2E","W2E","W2N","S2E","E2N","N2W","W2S"
};
for (int i = 0; i < derections.length; i++) {
new Road(derections[i]);
}
new LampController();
}
}
五.總結
用到的知識
1.List集合,用於儲存每條路線上的車輛。
2.Executors類Executors.newSingleThreadExecutor(),建立新的執行緒,隨機在路線上增加車輛。
3.匿名內部類物件。在給執行緒的執行器傳遞任務時,建立了實現Runnable介面的子類的匿名內部類物件,匿名內部類
要訪問外部類的成員變數時,可以寫成:外部類名.this.成員變數名。
4.定時器timer。
ScheduledExecutorService timer =Executors.newScheduledThreadPool(1)
建立一個定時器。以及在LampController類中的控制紅綠燈時間的定時器。
5.列舉。在編寫Lamp類時使用了列舉,保證了無論什麼時候呼叫的燈都是同一個燈。
但是要注意,列舉先定義,再使用。
6.建立燈物件時,要是一個一個燈的new,要new12個物件,很麻煩。可以使用循來建立例項物件,只要傳遞燈的名字即可。