1012 C++ 程式作業二 (due 102/06/06 (四) 23:59) :

修車廠管理系統 -- 程式撰寫指引 - 第三階段

你看到這個說明文件的原因可能有兩個:

  1. 看了上一個階段的設計提示你的作業還是沒有交出來, 程式有很嚴重的 bug, 實在不知道作業在要求什麼, 光光有抽象的設計還是沒有辦法下手, 但是第二階段繳交時間已經過了, 你想要繼續下一階段的作業, 必須完成這個作業

  2. 你已經繳交初步的作業, 但是自己覺得不太能夠掌握設計的方法, 所以來看看別人的設計, 驗證一下子自己的設計, 也順便看一下程式碼, 多了解別人程式的寫法, 從程式碼裡面吸收一些經驗

物件導向的設計是漸進式的, 並不是在紙上設計完所有的機制然後在進入 "程式碼實作 (coding)" 階段的, 越有經驗的設計者在 物件導向分析和設計的階段會做得比較多, 會透過 UML 中的 use case, class diagram, sequence diagram, state diagram, collaboration diagram 得到一個比較接近最後軟體的系統架構設計, 然後進入 coding 的階段, 不過目前你還在學習 C++ 的語法, 如果先設計好了架構, 也不知道是不是真的可以實作出來, 所以下面使用的方法其實就是基本的 bottom-up 設計, 由比較基礎比較簡單的物件開始設計, 慢慢地組合一些物件、調整一些物件的功能來得到最後需要的功能, 在過程中逐步修改各個物件的設計。

 




1. 怎樣運用C++函式庫中現成的類別 vector, deque, istream, ostream

2. 怎樣建立單一的類別 (建構元, 解構元, 拷貝建構元, 設定運算子, 檔案序列化, ...)

3. 怎樣結合多個類別的物件來模擬實際世界中系統的運作, 提供自動化的輔助系統

4. assert 應用

5. unitTest()

後續可以提供你練習物件導向技術的基礎平台, 練習繼承與多型的設計方法, 進一步結合資料庫系統或是圖形化介面系統開發應用程式

 
說明

通常客戶開車進到修車廠中, 或是車子被拖入修車廠中, 資深的技師很快地幫你評估需要做怎樣的維修或是哪些項目的保養, 然後根據目前在等候維修的車輛、可用的設備狀況、以及今天上班的技師的數量, 給你一個預估的完修時間以及初步的報價, 由於現在物料控管以及物流系統都很發達, 所以各種車款的各種維修零件都建立了電腦系統, 可以查詢到所需要的物料的價格, 修車廠中對於各種車款的故障狀況以及保養程序也在電腦中建立了標準流程, 所以技師只要鍵入基本的客戶車輛資料, 選擇維修的項目, 程式就可以自動推算出預估的取車時間以及初步的報價, 這個作業就是嘗試建立這樣的一套系統 。

在這個作業裡稍微做一些簡化, 以便你更容易實作需要的類別:

1.

物料的價格、品名、與系統內部編號我們簡化如下表:

物料編號 品名 售價 維修時是否需舉升車體 保固期 (月)
a24 頭燈燈泡 2000 0 12
a25 前燈罩 600 0 12
a26 空氣濾清器 1200 0 12
a31f 前門中控鎖 2600 0 24
...        
c01 輪胎 2200 1 6
..        

上述這些資料我們先假設是固定的, 由資料檔案裡讀出, 檔案內容如下:

          14            本檔案中物料個數
          a24           物料編號
          頭燈燈泡      品名
          2000          售價
          0             維修時是否需舉升車體
          12            保固期 (月)
          a25
          前燈罩
          600
          0
          12
          a26
          ... 

其中第一列為檔案中資料的筆數, 其餘資料分別為每一 "料件" 的 物料編號, 品名, 售價, 維修時是否需要舉升車體, 保固期

測試資料檔 Parts.txt

這個資料檔案裡面的每一筆資料是獨立的, 我們設計一個 Part 類別來存放每一筆物料, 這個類別裡需要依據資料的格式來設計資料成員如下

    string m_id;
    string m_name;
    int  m_price;
    int  m_warrantyMonths;
    bool m_liftedRequired;

照理說物料的資料應該還要有供應商的名稱, 物料的進價等等, 不過這裡簡化掉了

接下來你應該要設計 "序列化 (Serialization)" 的建構程式碼 Part(istream &is), 由資料檔案中讀出一筆一筆的資料, 建構出一個一個 Part 物件, 並且把這些物件記錄在一個容器裡面

你也應該順便寫一個列印 Part 物件裡資料內容的 print(ostream &os) 函式, 一個簡單的單元測試函式 unitTest() 來讀取檔案並且列印所有讀到的資料, 在 main() 裡面呼叫單元測試確定這一段程式的正確性

Part 類別的其他功能性介面目前都還沒有設計到, 稍後我們看到可以針對需要的功能再來設計

在目前的設計裡, 這個表格是整個修車廠只需要維護一份的, 裡面的 Part 物件應該也是只有一份的, 不應該會拷貝很多份, 不過實際狀況裡, 這個表格卻是會變動的, 可能會增加項目, 可能會刪除項目, 可能價格會更動, 也許在變動的過程裡需要要求每一個物料有唯一的編號, 並且需要不斷地修改檔案... 不過目前暫不考量這些細節

2.

接下來是 "維修或是保養項目" 的設計, 我們簡化如下表:

維修項目代號 需更換物料 維修時間 (分鐘) 工資
1001 a31f a32 b01 20 700
...      
2001 c01 c02 c03 d02 60 2500
...      
       

上述這些資料我們先假設是固定的, 由資料檔案裡讀出, 檔案內容如下:

          10               本檔案中維修項目總個數
          1001             維修項目代號
          3                維修物料個數
          a31f             需更換料號1
          a32              需更換料號2
          b01              需更換料號3
          20               標準維修時間 (分鐘)
          700              工資  
          1002
          2
          a31r
          b02
          30
          900
          ...
其中第一列為檔案中資料的筆數, 其餘資料分別為每一 "維修項目" 的 維修項目代號, 維修物料個數 n, 需更換料號1, 需更換料號2, ...,需更換料號 n, 標準維修時間 (分鐘), 工資

測試資料檔 MaintenanceItems.txt

這個資料檔案裡面的每一筆 "維修項目" 資料是獨立的, 我們設計一個 MaintenanceItem 類別來存放每一筆維修項目, 這個類別裡需要依據資料的格式來設計資料成員如下:

        int m_id;
        vector<char *> m_parts;
        int m_duration;
        int m_laborCharge;

在這個設計裡, m_parts vector 記錄的是所需要更換物料的 ID, 這也是一個簡化的實作, 以後每次要透過 MaintenanceItem 物件來存取物料時, 都要在物料的表格中搜尋一次, 比較有效率的方法應該是只記錄對應物料物件的指標

接下來你應該要設計 "序列化 (Serialization)" 的建構程式碼 MaintenanceItem(istream &is), 由資料檔案中一筆一筆地讀出資料, 建構出一個一個 MaintenanceItem 物件, 並且把這些物件記錄在一個容器裡面

你也應該順便寫一個列印 MaintenanceItem 物件裡資料內容的 print(ostream &os) 函式

不過這個時候你可能發現 print() 函式裡只能列印各個需要更換物料的 "代碼", 除非每一個 MaintenanceItem 可以存取到物料的表格, 否則沒有辦法列印出 物料的名稱, 所以我們稍微更改一下 MaintenanceItem 類別的設計, 在類別中增加一個 指標變數指到 "物料表格": vector<Part> *const m_ptrPartsTable; 另外在建構元也傳入這個物料表格的指標 MaintenanceItem(istream &is, vector<Part> *const ptrPartsTable), 注意: 建構 維修項目表格之前不一定需要先建構好 物料表格, 只需要把物料表格那個容器物件的指標傳進來就好了。接下來修改 print(ostream &os) 函式, 除了可以印出 m_id, m_duration, m_laborCharge 之外, 也可以藉由 "物料表格" 查詢到所有物料的價格, 也就是可以作出一個 getTotalCost() 的函式來負責查詢及加總, 這個函式在這個階段裡可以暫時先放在 private 區段, 不過等一下在設計 "維修單 (MaintenanceOrder)" 物件時, 很快就會發現也需要這個功能, getTotalCost() 需要是 MaintenanceItem 的介面的一部份; 另外配合 MaintenanceItem::print(ostream &os) 函式的設計, 也需要一個 MaintenanceItem::printPartName(char *id, ostream &os) 的輔助函式, 負責查詢物料表格, 並且新增 "物料 (Part)" 類別一個 Part::printNameCost(ostream &os) 的介面來印出每一個物料的名稱和單價。

接下來撰寫一個單元測試函式 unitTest(), 先由檔案 Parts.txt 中建構一個 "物料表格", 再由檔案 MaintenanceItems.txt 中建構一個 "維修項目表格", 並且列印所有讀到的資料, 最後在 main() 裡面呼叫單元測試確定這一段程式的正確性

============= List of Maintenance items =============
ID=1001   Duration=20   Labor cost=NT  700   Total cost= 4800
   Parts=[a31f([前門中控鎖,2600]),
          a32([自動變速箱油,1200]),
          b01([儀表燈,300])]
ID=1002   Duration=30   Labor cost=NT  900   Total cost= 4050
   Parts=[a31r([後門中控鎖,2600]),
          b02([車窗馬達,550])]
ID=1003   Duration=20   Labor cost=NT  800   Total cost= 3450
   Parts=[b01([儀表燈,300]),
          b02([車窗馬達,550]),
          b03([電瓶,1800])]
ID=1004   Duration=40   Labor cost=NT 1200   Total cost= 3850
   Parts=[a25([前燈罩,600]),
          a32([自動變速箱油,1200]),
          b01([儀表燈,300]),
          b02([車窗馬達,550])]
ID=1005   Duration=40   Labor cost=NT 1200   Total cost= 7900
   Parts=[a24([頭燈燈泡,2000]),
          a31f([前門中控鎖,2600]),
          b01([儀表燈,300]),
          b03([電瓶,1800])]
ID=1006   Duration=40   Labor cost=NT 1200   Total cost= 3850
   Parts=[a25([前燈罩,600]),
          a26([空氣濾清器,1200]),
          b01([儀表燈,300]),
          b02([車窗馬達,550])]
ID=2001   Duration=60   Labor cost=NT 2500   Total cost=12150
   Parts=[c01([輪胎,2200]),
          c02([煞車碟盤,3000]),
          c03([避震器,3600]),
          d02([驅動皮帶,850])]
ID=2002   Duration=20   Labor cost=NT  400   Total cost= 7850
   Parts=[c02([煞車碟盤,3000]),
          c03([避震器,3600]),
          d02([驅動皮帶,850])]
ID=2003   Duration=25   Labor cost=NT  500   Total cost= 1350
   Parts=[d02([驅動皮帶,850])]
ID=2004   Duration=70   Labor cost=NT 1500   Total cost= 8500
   Parts=[c01([輪胎,2200]),
          c03([避震器,3600]),
          d01([引擎機油,1200])]
=====================================================
============= List of Parts =============
[      頭燈燈泡] ID=  a24  price=NT2000  warranty= 12mo
[        前燈罩] ID=  a25  price=NT 600  warranty= 12mo
[    空氣濾清器] ID=  a26  price=NT1200  warranty= 12mo
[    前門中控鎖] ID= a31f  price=NT2600  warranty= 24mo
[    後門中控鎖] ID= a31r  price=NT2600  warranty= 24mo
[  自動變速箱油] ID=  a32  price=NT1200  warranty=  3mo  must be lifted
[        儀表燈] ID=  b01  price=NT 300  warranty= 24mo
[      車窗馬達] ID=  b02  price=NT 550  warranty= 24mo
[          電瓶] ID=  b03  price=NT1800  warranty=  3mo
[          輪胎] ID=  c01  price=NT2200  warranty=  6mo  must be lifted
[      煞車碟盤] ID=  c02  price=NT3000  warranty= 12mo  must be lifted
[        避震器] ID=  c03  price=NT3600  warranty= 24mo  must be lifted
[      引擎機油] ID=  d01  price=NT1200  warranty=  0mo
[      驅動皮帶] ID=  d02  price=NT 850  warranty=  6mo  must be lifted
=========================================

和前一步驟的 Part 類別一樣, MaintenanceItem 類別的其它功能性介面目前都還沒有設計到, 稍後我們看到可以針對需要的功能再來設計

注意: 在上述的實作程式裡, "物料表格" 和 "維修項目表格" 直接用 C++ 標準函式庫裡的物件實作, 並沒有另外設計 PartsTable 或是 MaintenanceItemsTable 這樣的類別, 以致於像 "查詢具有某一 ID 的物料的名稱和價格" 這樣的功能沒有適當的地方可以放, 只好放在 MaintenanceItem 類別中當成一個 private 的 輔助函式 MaintenanceItem::printPartName(char *id, ostream &os), 如果有一個 PartsTable, 這個函式應該是它的介面才對。

3.

修車廠

設計完上面兩個類別以後, 你也看到了應該要有兩個容器物件來代表 物料的表格以及維修項目的表格, 這兩個容器應該設計在哪裡? Global? (當然不是) main() (應該也不是, 如果所有東西都放在 main 函式裡, 程式功能就很難增加了), 可以設計一個 "修車廠 (Garage)" 的物件, 用這個物件的資料成員來管理上面的表格, 同時這個物件也會提供使用者介面讓使用者可以輸入維修的車輛的資料, 稍後會對每一輛進廠維修的車輛都產生一個 "維修單" 物件, 這些維修單物件也會記錄在一個容器物件中, 如此使用者可以隨時查詢維修車輛的狀況。這個物件至少需要有下列的成員:

    vector<Part> m_parts;
    vector<MaintenanceItem> m_items;
    vector<MaintenanceOrder *> m_orders;

前兩個就是 物料的表格 m_parts 以及維修項目的表格 m_items, 第三個是記錄維修單的容器, 在這裡我設計成記錄 "維修單 (MaintenanceOrder)" 指標的容器, 其實一開始也是用記錄 MainenanceOrder 物件的容器, 但是後來改成指標了,主要的原因是因為 "維修單" 物件會設計成有唯一序號的, 因此不希望設計拷貝建構元, 不希望在 push_back() 進 m_orders 容器時產生額外暫時性的拷貝, 這些拷貝也會有序號, 就會使得序號跳的很快, 很不正常, 所以要避免 push_back() 拷貝參數的資料, 就需要設計成記錄 MainenanceOrder 物件指標的容器。

這個類別的建構元函式 Garage() 主要就是由兩個檔案中建立起 物料的表格以及維修項目的表格

稍後還會對這個 Garage 物件增加使用者介面的函式以及如何設計 "待修隊伍" 的部份。

如果程式功能持續擴大下去, 也許一個程式可以管理好幾個分廠, 物料, 維修設備, 以及技師也許可以分享, 也還要設計上述表格的維護介面以及修改過的存檔功能...

4.

日期與時間

下面是範例執行程式在執行時的輸入資料範例:
          2013 5 1
          S
          9 0
          N
          9 20 
          8934-NX 
          2 
          1002 1004
          ...
其中紅字是日期的輸入, 藍字是時間的輸入 (本來像這樣的應用程式應該是不需要輸入日期和時間的, 應該是讀取當時的系統日期和時間就可以的, 不過在設計與測試這樣的程式時, 你沒有辦法在實際運作時測試, 否則你真的要執行一整天才能測試 20 筆維修, 測試的資料恐怕太少了, 會有很多的狀況測試不到)

日期和時間的物件在這個程式裡的作用是什麼呢? 一是每一筆維修單裡面一定都有記錄日期和時間, 如果單單考慮預估完修時間的程式要求的話, 好像不需要記錄日期, 不過如果你考慮到也許以後會加上過去維修資料查詢的功能, 一個客戶的車輛進廠維修了很多次, 如果不記錄日期的話, 如何分辨是哪一次的維修, 維修了哪些項目, 如何去查詢各個更換零件的保固期?

因為需要預估 "完修時間", 所以你的時間類別需要支援加法以及比較大小, 目前的測試資料裡並沒有需要好多天的維修項目, 所以你的日期類別目前還不需要支援加法, 不過如果你考慮到將來可能會有多天的維修, 也許你可以把日期和時間合併在一個類別裡面, 加法的進位會比較簡單, 例如:

class DateTime  
{
public:
    DateTime(int year, int month, int day, int hour, int minute);
    bool operator<(const DateTime &rhs) const;
    void operator+=(const int duration);
private:
    int m_year;
    int m_month;
    int m_day;
    int m_hour;
    int m_minute;
};

為了正確地讓日期可以進位, 類別裡也需要設計一個靜態陣列記錄每個月的天數, 當然也需要測試某一年是否閏年, 例如:

在 DateTime.h 中

class DateTime
{
    ...
private:
    bool isLeap(int year) const;
    static int nDays[12];
    ...
};
在 DateTime.cpp 中配置 nDays 的記憶體並且初始化
int DateTime::nDays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

另外日期和時間一定要可以顯示出來的, 所以列印的功能可以先設計好

為了顯示月份, 類別裡也設計一個 月份名稱的靜態字元陣列

public:
    void print(ostream &os) const;
private:
    static char monthNames[12][5];
在 DateTime.cpp 中配置 monthNames 的記憶體並且初始化
char DateTime::monthNames[12][5] = {"Jan.", "Feb.", "Mar.", "Apr.", "May", "June",
                                    "July", "Aug.", "Sep.", "Oct.", "Nov.", "Dec."};
5.

維修單

客戶把車輛開到保養廠時, 技師評估過以後會填寫一張維修單, 載明維修的車輛, 維修的項目, 預估的取車時間, 以及報價

在範例執行程式執行時的輸入資料範例中可以看到目前簡化的資料需要包括哪些欄位:

          2013 5 1
          S
          9 0
          N
          9 20 
          8934-NX 
         2 
         1002 1004
           ...
其中紅字部份包括 車號, 維修項目個數m, 維修項目1, 維修項目2, ..., 維修項目m

這些是 "維修單 (MaintenanceOrder)" 類別需要包括的資料成員, 另外當然也需要有 "進廠時間", "完修時間", "維修費用" 等欄位, 因為需要查詢 "維修單表格", 所以也設計一個連結的指標變數, 以及一個列印的介面, 例如:

class MaintenanceOrder  
{
public:
    MaintenanceOrder(string licensePlate, DateTime timeRequested,
                     int numWorks, int workIDs[], 
                     vector<MaintenanceItem> *ptrMITable);
    void print(ostream &os) const;
private:
    int m_id;
    string m_licensePlate;
    vector<int> m_workIDs;
    int m_totalCharge;
    DateTime m_timeRequested;
    DateTime m_timeCompleted;
    vector<MaintenanceItem> *m_ptrMITable;

    static int serialNo;
};

其中 m_id 是一個唯一的編號, 永遠不會重複, 所以你可以運用 static member variable: serialNo 來設計

每當客戶的車輛進廠維修時就會產生一個 MaintenanceOrder 的物件, 這些物件當然也需要記錄在容器 Garage::m_orders 裡, 如此一天下來使用者可以隨時查詢某一張維修單的狀況, 車廠也可以計算每天的營收, 如果進一步車廠有維護客戶的資料庫的話, 也應該把維修的資料記錄在客戶資料庫裡

這個程式要求在客戶車輛進廠時就計算下列資料提供客戶:

  1. 預估維修金額
  2. 預估完修時間 (取車時間)
接下來的步驟就是要完成這兩個要求
6.

預估維修金額

首先在維修單類別應該要提供計算總價的功能, 總價包括每一個維修項目所需要的物料的價格以及維修的工資, 所以維修單類別應該要能夠存取 步驟 2 中記錄所有 維修項目的表格, 如此才能夠知道每一個維修項目需要更換哪些物料, 需要多少工資, 另外也需要能夠存取 步驟 1 中記錄所有物料的表格, 才能知道每一個物料的價格, 在上一個步驟裡設計的 MaintenanceOrder 類別目前沒有辦法直接存取到這兩個容器

第一種修改上面類別的方法是在 "修車廠" 類別中增加取得這兩個表格的介面, 需要計算總價時就跟修車廠物件要這兩個表格的物件參考, 不過就算這樣, 維修單類別還是需要修改, 需要記錄 "修車廠" 物件的參考, 如此才能夠跟修車廠物件要這兩個表格的物件參考: 需要在 "維修單" 類別中增加一個修車廠物件的參考或是指標, 在建構每一個維修單時需要做為建構元的參數

第二種修改的方法是直接在 "維修單" 類別中增加 "維修項目" 表格物件的參考或是指標, 修改 "維修單" 類別的建構元, 在建構每一個維修單時需要把 "維修項目" 表格物件的參考 做為建構元的參數; 另外也修改 "維修項目" 類別, 在維修項目物件中增加一個記錄 "物料" 表格物件的參考或是指標, 修改 "維修項目" 類別的建構元, 在建構每一個維修項目物件時需要把 "物料" 表格物件的參考或是指標 做為建構元的參數, 例如:

    class MaintenanceOrder
    {
    public:
        ...
        MaintenanceOrder::MaintenanceOrder(..., vector<MaintenanceItem> *ptrMITable, ...);
        ...
    private:
        ...
        vector<MaintenanceItem> *m_ptrMITable;
        ...
    };
    
    MaintenanceOrder::MaintenanceOrder(..., vector<MaintenanceItem> *ptrMITable, ...)
        : ..., m_ptrMITable(ptrMITable),...
    {
       ...
    }

    class MaintenanceItem  
    {
    public:
        ...
        MaintenanceItem::MaintenanceItem(..., vector<Part> *ptrPartsTable, ...);
        ...
    private:
        ...
        vector *const m_ptrPartsTable;
        ...
    };

    MaintenanceItem::MaintenanceItem(..., vector<Part> *ptrPartsTable, ...)
        : ..., m_ptrPartsTable(ptrPartsTable),...
    {
       ...
    }
(在前幾步驟的實作程式碼程式碼裡面都已經是使用這樣的設計了) , 另外因為目前總價是一個固定的數字, 所以在建構 MaintenanceOrder 物件的時候就可以直接算出來了
7.

預估完修時間

這一次的作業裡你可以先假設只有一個技師, 一套維修的設備, 所以所有進廠的車輛都需要排在待修隊伍中一輛一輛地順序維修 (後續你可以進一步設計比較複雜的維修策略, 多種不同的維修項目可以有多組技師同時進行...), 假設給機具和維修技師 10 分鐘的休息時間, 假設實際維修時間是標準維修時間的 80%-120%

  1. 如步驟 4 所說明, 系統目前的時間請以 "新客戶到達時輸入的時間" 或是 "查詢維修狀態時輸入的時間" 為準
  2. 請注意系統運作時上述兩種輸入的時間值是遞增的 (你可以參考下面的命令列測試資料)
  3. 每當系統時間改變時, 請 修正/重新估計 在待修隊伍中還沒有維修完的車輛的 "完修時間", 已經修完的車輛的完修時間已經記錄下來, 不會再更改了
  4. 每一張維修單的 "預估取車時間" 可以用最多的時間 (120% 標準維修時間) 來估計, 如此實際的取車時間會比預估的時間早

要完成上述的功能, 在 "修車廠" 類別中應該要增加一個 "待修隊伍" 的容器物件, "維修單" 物件一產生出來以後應該就要放進這個 "待修隊伍" 物件中, 已經修完的車輛應該在修改維修單中的 "完修時間" 欄位以後由 "待修隊伍" 中移除, 把 "待修隊伍" 中下一個維修單的 "開始維修時間" 設為前一輛車完修時間加上十分鐘, 這個 "待修隊伍" 物件不見得要自己設計 (雖然也很簡單), 不過可以多練習使用 C++ 標準函式庫中的 queue 物件或是 deque 物件, 由於我們在程式進行中需要常常順序把 "待修隊伍" 中的維修單看過一遍, 所以其實 queue 類別是不符合需要的, 因為它不提供 iterator
8.

待修隊伍

你可以運用下面的 iterable_queue 類別, 這個類別擴充標準函式庫中的 queue 類別如下

    #include <queue>
    #include <deque>
    #include <iostream>

    template<typename T, typename Container=std::deque<T> >
    class iterable_queue : public std::queue<T,Container>
    {
    public:
        typedef typename Container::iterator iterator;
        typedef typename Container::const_iterator const_iterator;

        iterator begin() { return this->c.begin(); }
        iterator end() { return this->c.end(); }
        const_iterator begin() const { return this->c.begin(); }
        const_iterator end() const { return this->c.end(); }
    };
把上面這段程式放在 iterable_queue.h 中, 使用範例如下:
    #include "iterable_queue.h"
    #include <iostream>
    ...
    int i;
    iterable_queue<int> int_queue;
    for(i=0; i<10; ++i)
        int_queue.push(i);

    iterable_queue<int>::iterator it;
    for(it=int_queue.begin(); it!=int_queue.end(); ++it)
        std::cout << *it << "\n";

    while (!int_queue.empty())
    {
        std::cout << int_queue.front() << endl;
        int_queue.pop();
    }
或是直接運用底層雙向的 deque 類別, 簡單的使用範例如下:
    #include <iostream>
    #include <deque>
    using namespace std;
    ...
    deque<int> int_queue;
    int myint;

    cout << "Please enter some integers (enter 0 to end):\n";

    do 
    {
        cin >> myint;
        int_queue.push_back(myint);
    } 
    while (myint);

    cout << "int_queue contains: ";
    while (!int_queue.empty())
    {
        cout << " " << int_queue.front();
        int_queue.pop_front();
    }

    deque<int>::iterator it;
    for(it=int_queue.begin(); it!=int_queue.end(); ++it)
        std::cout << *it << "\n";
在 Garage 類別中用 deque<MaintenanceOrder *> m_serviceQueue 資料成員來實作 "待修隊伍"

如此可以使用 deque<>::push_back(), deque<>::pop_front(), deque<>::front(), deque<>::iterator, deque<>::begin() 以及 deque<>::end() 等介面來操作這個 "待修隊伍"

9.

使用者介面:

下面為使用者操作這個應用程式的範例:

D:\assign2>garage

Enter today's date: (year, month, day) 2013 5 1

[S]tatus update/[N]ew request? S
Current time? (hour, minute) 9 0

There is no car currently being maintained!

[S]tatus update/[N]ew request? N
Service request time: (hour, minute) 9 20 
License Plate: 8934-NX 
Number of Service Items: 2 
Service Items: 1002 1004
Expected finish time: 2013/05/01 10:44

[S]tatus update/[N]ew request? S
Current time? (hour, minute) 9 30

[0000] 8934-NX, Time requested:  2013/05/01 09:20 ,
                Service requested: 1002, 1004,
                Service started on 2013/05/01 09:20 ,
                Charge=NT7900
   ===> Expected finish time:  2013/05/01 10:44

[S]tatus update/[N]ew request? N
Service request time: (hour, minute) 9 50
License Plate:  1234-AB 
Number of Service Items: 2 
Service Items: 1003 2001
Expected finish time: 2013/05/01 12:30

[S]tatus update/[N]ew request? S
Current time? (hour, minute) 10 10

[0000] 8934-NX, Time requested:  2013/05/01 09:20 ,
                Service requested: 1002, 1004,
                Service started on 2013/05/01 09:20 ,
                Charge=NT7900
   ===> Expected finish time:  2013/05/01 10:44
[0001] 1234-AB, Time requested:  2013/05/01 09:50 ,
                Service requested: 1003, 2001,
                Service not yet started.,
                Charge=NT15600
   ===> Expected finish time:  2013/05/01 12:30

[S]tatus update/[N]ew request? N
Service request time: (hour, minute) 17 20 
License Plate: 4444-RR 
Number of Service Items: 2 
Service Items: 2003 2004
Expected finish time: 2013/05/01 19:14
Sorry, it's too late to accept this request! [S]tatus update/[N]ew request? S Current time? (hour, minute) 17 30 [0000] 8934-NX, Time requested: 2013/05/01 09:20 , Service requested: 1002, 1004, Service started on 2013/05/01 09:20 , Service finished on 2013/05/01 10:39 , Charge=NT7900 [0001] 1234-AB, Time requested: 2013/05/01 09:50 , Service requested: 1003, 2001, Service started on 2013/05/01 10:49 , Service finished on 2013/05/01 12:20 , Charge=NT15600 There is no car currently being maintained! [S]tatus update/[N]ew request? S Service request time: (hour, minute) 17 31

上面紅字部份為使用者輸入的資料, 其他為程式的輸出

系統啟動時先輸入當天的日期, 例如: 2013 5 1

接下來使用者有兩種選擇:

  1. S 代表希望知道目前 修車廠中 已完修維修單以及 "待修隊伍" 中維修單的資料
  2. N 代表有一輛車進廠維修, 需要產生維修單

如果使用者在產生新的維修單時輸入的資料, 得到的預估完修時間超過 17:30, 系統不會接受這一份維修單

如果使用者輸入一個超過 17:30 的時間, 這時車廠已經準備關門, 如果 "待修隊伍" 中沒有維修單了 (正常狀況下應該是沒有了, 因為在產生維修單的時候我們用最多的時間來估計它的完修時間, 所以實際完修時間一定在 17:30 以前), 系統就結束。

在 Garage 類別中加上和使用者互動的函式 void Garage::acceptServiceRequests(), 主要處理 N 和 S 命令, 例如:

主要架構

計算及列印 "完修時間"

Garage::updateServiceQueue(DateTime curTime)

其中計算及列印 "完修時間" 需要 MaintenanceOrder 類別提供 getStartTime() 介面來查詢某一維修單開始維修的時間, 也需要 MaintenanceOrder 類別提供 maxDuration() 介面來計算最保守的維修時間

其中 updateServiceQueue 需要 MaintenanceOrder 類別提供 isFinished(DateTime curTime) 介面來查詢是否該維修單已經完修, 也需要 MaintenanceOrder 類別提供 completedTime() 介面來查詢某一維修單的完成時間, 還需要提供 startWorking(DateTime startingTime) 介面來設定某一維修單的實際開始維修時間

範例執行程式

10.

其他沒有完整考量的功能:

  1. 在上面的實作中, 如果有一輛車在 10:00 進廠, 這時剛好沒有車輛在維修中, 可是前一輛車是 09:55 才維修完的, 依照我們描述的修車廠運作方法, 應該是 10:05 才會開始維修, 並不是 10:00 就可以開始維修
 

C++ 物件導向程式設計課程 首頁

製作日期: 05/16/2013 by 丁培毅 (Pei-yih Ting)
E-mail: pyting@mail.ntou.edu.tw TEL: 02 24622192x6615
海洋大學 電機資訊學院 資訊工程系 Lagoon

height=100>