1. 程式人生 > >黑馬程式設計師-----7K面試題之交通燈系統

黑馬程式設計師-----7K面試題之交通燈系統

------<a href="http://www.itheima.com" target="blank">Java培訓、Android培訓、iOS培訓、.Net培訓</a>、期待與您交流! -------

現在每晚都加大力度去學習和加強自己的JAVA基礎。而今晚我就通過張老師的視訊,去學習7K面試題的交通燈系統。在視訊中,我認為重要的是面向物件程式設計的思想,程式碼知識跟著思想一步一步敲出來的,思路清晰了,程式碼自然就不是問題了。張老師反覆的一句話很重要:誰擁有資料,誰就對外提供操作這些資料的方法。我一定要趕上深圳70期! 下面我把我的學習過程記錄下來。

一、概述

模擬實現十字路口的交通燈管理系統邏輯,具體需求如下:(需求直接來源於老師的文件)

① 非同步隨機生成按照各個路線行駛的車輛。

例如:

由南向而來去往北向的車輛 ---- 直行車輛

由西向而來去往南向的車輛 ---- 右轉車輛

由東向而來去往南向的車輛 ---- 左轉車輛

。。。

② 訊號燈忽略黃燈,只考慮紅燈和綠燈。

③ 應考慮左轉車輛控制訊號燈,右轉車輛不受訊號燈控制。

④ 具體訊號燈控制邏輯與現實生活中普通交通燈控制邏輯相同,不考慮特殊情況下的控制邏輯。

注:南北向車輛與東西向車輛交替放行,同方向等待車輛應先放行直行車輛而後放行左轉車輛。

⑤ 每輛車通過路口時間為1秒(提示:可通過執行緒Sleep的方式模擬)。

⑥ 隨機生成車輛時間間隔以及紅綠燈交換時間間隔自定,可以設定。

⑦ 不要求實現GUI,只考慮系統邏輯實現,可通過Log方式展現程式執行結果。

二、需求分析

縱觀整個系統的需求我們可以得出以下結論:在一個十字路口,涉及到了車、燈以及路,可以把他們之間的關係模擬以下,把路設計成為一個儲存車輛的容器,車輛來了,存入容器內,等到車輛將要通過某一條路上的紅綠燈亮起,車輛啟動,相當於從容器中取出車輛,通過十字路口,下面再仔細地介紹:

\

四面拐彎的設為永遠都是綠燈,也就是說四面什麼時候都可以轉,也就是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條線路,呼叫交通燈的總控制類,使得可以在每條線路隨機增加汽車且調動了紅綠燈來控制車輛之間的來往