經典面試專案--交通燈管理系統
專案由來:
該專案原本是軟通動力的一道面試題,交由面試者帶回去自行完成,稽核通過後即通過面試,當然現在不可能再作為面試題了。不過這個專案還是非常有實踐意義的,在網路上傳播廣泛,從中我們可以學習面向物件的程式設計精髓,對於掌握Java SE基礎的初學者而言意義更甚。好了,下面就具體看一看這個專案。
非同步隨機生成按照各個路線行駛的車輛。
例如:
由南向而來去往北向的車輛 ----直行車輛
由西向而來去往南向的車輛 ----右轉車輛
由東向而來去往南向的車輛 ----左轉車輛
。。。
訊號燈忽略黃燈,只考慮紅燈和綠燈。
應考慮左轉車輛控制訊號燈,右轉車輛不受訊號燈控制。
具體訊號燈控制邏輯與現實生活中普通交通燈控制邏輯相同,不考慮特殊情況下的控制邏輯。
注:南北向車輛與東西向車輛交替放行,同方向等待車輛應先放行直行車輛而後放行左轉車輛
每輛車通過路口時間為1秒(提示:可通過執行緒Sleep的方式模擬)。
隨機生成車輛時間間隔以及紅綠燈交換時間間隔自定,可以設定。
不要求實現GUI,只考慮系統邏輯實現,可通過Log方式展現程式執行結果。
思路分析與程式設計
從專案需求中可以發現主要有交通燈、各個路線的行駛車輛兩大類。
路線分析以及時間設定:結合現實情況分析可知一個方向向其他三個方法各有一條路線,既然是十字路口那麼線路總共是3x4=12條。
紅綠燈規則:右轉訊號燈常綠,每輛車通過路口的時間為1秒鐘,並且後面有緊隨其後開來的車,這裡我們設定隨機生成一個車輛的時間為2-10
比如從南向北以及對向直行綠燈時間到了轉為紅燈,那麼同一時間從南向西以及對向左轉紅燈時間到轉為綠燈,綠燈時間到轉為紅燈之後,在同一個時間從西向東以及對向直行紅燈時間到轉為綠燈,綠燈時間到轉為紅燈…如此往復迴圈。所以紅綠燈需要一個控制器來控制,我們在程式設計時就單獨用一個類來實現這個控制器。由於一個線路上的燈與其對向線路的燈規則完全一致,並且右轉綠燈常亮。根據這一現象,我們只需要控制四個方向的燈即可。
綜合上面的分析,我們可以大致明白要實現哪些類了,分別為:
1,Road類,代表行駛路線的類,由於這裡每條線路都會隨機生成一些車輛,所以我們就不需要單獨列出一個表示車輛的類了,直接作為路線類的成員變數即可。
2,Lamp類,表示每條線路上的紅綠燈的類,由於這裡只有固定的12條線路,不能再單獨列出一個線路,所以使用列舉定義該類最為合適。
3,LampController類,控制Lamp物件。
4,一個執行程式的主類。
下面就對各個類進行具體分析需要實現哪些功能吧。
Road類,
1, 每條線路要有表示自己的名稱的屬性,自然選擇String。
2, 每條執行緒需要一個可以新增、刪除(分別對應車輛開到路口、通過路口之後開走)的表表示該線路上的車輛,自然選擇ArrayList。
3, 每隔2-10秒鐘開來一輛車,需要一個單獨的執行緒通過定時器實現。開來一輛車則裝入ArrayList列表中,開走一輛車則從列表中刪除。車輛使用線路名_ID標識。
4, 檢查對應燈的狀態,若為綠燈則每一秒鐘開走一輛車,從ArrayList列表中刪除,同樣需要建立一個單獨的執行緒。
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;
public Road(String name){
this.name = name;
//在構造方法中啟動一個定時器,每隔一秒檢查該方向上的燈是否為綠,
//是則列印車輛集合和將集合中的第一輛車移除掉。
//建立執行緒池,使用單個worker執行緒的Executor,以無界佇列方式來執行該執行緒。
ExecutorService pool = Executors.newSingleThreadExecutor();
//新建一個Runnable的實現類的物件,在未來某個時間執行給定的命令。
pool.execute(new Runnable() {
public void run(){
int id = 1;
while(true) {
//每隔2-10秒鐘駛來一輛車,利用執行緒休眠實現,新來的車新增進集合末尾,表示後來
try {
Thread.sleep((new Random().nextInt(9) + 2) * 1000);
} catch(InterruptedException e) {
e.printStackTrace();
}
vechicles.add(Road.this.name + "_" + id++);
}
}
});
//每隔一秒鐘檢查路上的燈是否為綠燈,若是,則通過一輛最前面的車,將車從集合中刪除
ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);
timer.scheduleAtFixedRate(
new Runnable(){
public void run(){
if(vechicles.size()>0){
boolean lighted = Lamp.valueOf(Road.this.name).isLighted();
if(lighted){
System.out.println(vechicles.remove(0) + " 駛過路口!");
}
}
}
}, //要執行的任務
1, //首次執行的延遲時間
1, //兩次操作之間的時間差
TimeUnit.SECONDS //設定時間的單位為秒
);
}
}
Lamp類,
1,固定十二條線路所以有對應的十二個Lamp例項,上面分析得到只需要控制其中的四條線路即可。
2,每個燈都有狀態顯示,綠(true)和紅(false),並且一條線路上的燈變紅或者變綠之後其對向線路上的燈要與之同步,下一條線路上的燈也要隨之作出反應變為綠燈。
3,由於直行和左轉綠燈時間不同,所以還需要引入方向(Direction)介面並增加一個屬性direction表示燈所在路線的方向
public interface Direction {
/*表示路線的行駛方法,有三個值,如下*/
public static final int LEFT = -1;
public static final int CENTER = 0;
public static final int RIGHT = 1;
int getDirection();
}
import java.util.Date;
enum Lamp implements Direction {
//每個列舉元素各表示一個方向的控制燈
S2N("N2S", "S2W", CENTER, false), S2W("N2E", "E2W", LEFT, false), E2W("W2E", "E2S", CENTER, false), E2S("W2N", "S2N", LEFT, false),
// 與上面宣告的四個方向的燈一一對應,被動執行即可
N2S(null, null, CENTER, false), N2E(null, null, LEFT, false), W2E(null, null, CENTER, false), W2N(null, null, LEFT, false),
// 右轉訊號燈常亮為綠燈
S2E(null, null, RIGHT, true), E2N(null, null, RIGHT, true), N2W(null, null, RIGHT, true), W2S(null, null, RIGHT, true);
private String opposite; //表示對向路線的燈
private String next; //表示下一條路線的燈
private boolean lighted; //燈的狀態
private int direction; //表示燈所在路線的方向,可能為LEFT\CENTER\RIGHT
private Lamp(String opposite, String next,int direction, boolean lighted){
this.opposite = opposite;
this.next = next;
this.direction = direction;
this.lighted = lighted;
}
public boolean isLighted(){
return lighted; //判斷燈的狀態,變亮表示綠燈,變黑表示紅燈
}
public int getDirection() { //獲取燈所在路線的方向
return direction;
}
// 將當前燈變為綠燈,同時其對向的燈一起隨之改變
public void light(){
this.lighted = true;
if(opposite != null){
Lamp.valueOf(opposite).light();
}
System.out.println(name() + " 綠燈亮了,下面總共應該有6個方向能看到汽車穿過!");
}
// 將當前燈和對應方向的燈變成紅燈,並且下一條路線上的燈變綠,返回這個變綠的燈
public Lamp blackOut(){
this.lighted = false;
if(opposite != null){
Lamp.valueOf(opposite).blackOut();
}
Lamp nextLamp = null;
if(next != null){
nextLamp = Lamp.valueOf(next);
nextLamp.light();
System.out.println("綠燈從" + name() + "-------->切換為" + next);
System.out.println("Time :" + new Date());
}
return nextLamp;
}
}
LampController類,
1, 整個系統中只需要一個交通燈控制器即可,所以,這個類應該設計為單例。
2, 執行時需要指定第一個為綠的燈,所以在構造方法中指定最好。
3, 實現兩個定時器分別控制直行和左轉的燈,直行綠燈時間20秒,左轉綠燈時間15秒
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
class LampController {
//表示當前燈
private Lamp currentLamp;
public LampController(){
//指定由南向北的燈首先為綠燈
currentLamp = Lamp.S2N;
currentLamp.light();
/*表示首次執行的延遲時間*/
int festTime;
if(currentLamp.getDirection() == Direction.CENTER) {
festTime = 20;
} else {
festTime = 15;
}
ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);
/*建立一個定時器,如果當前燈所在路線為直行路線則亮20秒綠燈,若是左轉路線則為15秒*/
timer.scheduleWithFixedDelay(
new Runnable() {
public void run() {
currentLamp = currentLamp.blackOut();
/*如果當前燈是直行路線上的燈則增加5秒鐘綠燈時間*/
if(currentLamp.getDirection() == Direction.CENTER) {
try {
Thread.sleep(5 * 1000);
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
},
festTime, //首次執行延時
15, //執行週期
TimeUnit.SECONDS //指定時間單位為秒
);
}
}
主類(TrafficDemo)執行程式
class TrafficDemo {
public static void main(String[] args) {
String[] directions = new String[]{
"S2N","S2W","E2W","E2S","N2S","N2E","W2E","W2N","S2E","E2N","N2W","W2S"};
// 建立12條路線
for(int i=0;i<directions.length;i++){
new Road(directions[i]);
}
// 交通燈控制器
new LampController();
}
}