1. 程式人生 > >以一個簡單的專案來學習面向物件程式設計(設計模式和多執行緒)

以一個簡單的專案來學習面向物件程式設計(設計模式和多執行緒)

下面的專案是兩年前學校老師佈置的一個小專案,當時自己用了一種很笨拙的方式實現了,現在用面向物件的思想和多執行緒重構這個專案。

問題描述:

西寶高速模擬模擬
西安市到寶雞市之間是我省主要的高速公路客運路線之一,經過簡化後的客運路線端點、中途停靠點和里程如下圖所示(括號裡是簡稱,里程的單位是公里):
示意圖

  • 限定條件
    (1) 從XN始發至BJ的客車和從BJ始發至XN的客車均有兩種車型:沃爾沃(限定乘客人數為40人);依維柯(限定乘客人數為21人)。沃爾沃的速度為2公里/分鐘,依維柯的速度為1.4公里/分鐘。
    (2) 起始狀態時,XN擁有沃爾沃和依維柯客車分別為XNW和XNY輛,BJ擁有沃爾沃和依維柯客車分別為BJW和BJY輛。
    (3) 從XN至BJ和從BJ至XN的沃爾沃,均為上午8:30開始,每小時一班,最後一班為下午5:30;從XN至BJ和從BJ至XN的依維柯,均為上午8:00開始,每20分鐘一班,最後一班為下午6:00。
    (4) 從XN至BJ的客車到達BJ後,即成為從BJ至XN的客車,排在當時BJ同類車型的隊尾,再按(3)確定發車時間;從BJ至XN的客車到達XN後的規則相同。
    (5) 只考慮途中只有乘客下車、沒有乘客上車的情況。
    (6) 有乘客下車時,不論方向與車型,停車時間統一為2分鐘。
    (7) 乘坐從XN至BJ客車的乘客,其下車點為XY、XP、WG、CP、GZ和BJ的可能性分別為P_XBXY、P_XBXP、P_XBWG、P_XBCP、P_XBGZ和P_XBBJ。這些可能性之和為1;乘坐從BJ至XN客車的乘客,其下車點為GZ、CP、WG、XP、XY和XN的可能性分別為P_BXGZ、P_BXCP、P_BXWG、P_BXXP、P_BXXY和P_BXXN。這些可能性之和為1。

  • 需模擬的活動
    (1) 從上午7:30開始到下午5:59為止,每分鐘分別在XN和BJ隨機產生去往BJ和XN方向的新到達的乘客。每分鐘達到的人數範圍為0~PN人。
    (2) 按照限定條件(7)的規定,隨機產生新到達的乘客的目的地。
    (3) 乘客按到達的先後順序上最近一輛(依照限定條件(3)的規定)始發的客車,若該車客滿則等候下一輛始發的客車。
    (4) 若客車到達中途停靠站時有乘客在此下車,按限定條件(5)和(6)處理,否則不停車繼續行駛。

我們逐步分析最關鍵的點:
我們先僅僅模擬一輛客車從西安到寶雞的過程,中途遇到的中間站停車2分,沒有乘客參與,僅僅是讓這輛客車從西安跑到寶雞。

這個簡單的問題,直觀的解決方案

是:一個大迴圈,每次迴圈時間更新一次,在迴圈內更新客車的位置,判斷客車時候到達中間站或終點站。這種解決方式思想簡單,但是可擴充套件性差,若有新種類的客車,則我們需要重新改寫主邏輯。

我們用面向物件的思維來分析這個簡單的模擬模擬過程,實際上就是客車啟動、行駛、中途停車、結束。這幾個狀態間的轉化。可以用狀態模式來解決這個問題。

思路:
客車類接收外界傳入的時間,其初始時呼叫啟動狀態指標,並把自己作為引數傳入,狀態類根據外界條件(時間)和規則(客車時刻表),來判斷出下個狀態是什麼(並更新客車類中儲存的狀態碼)完成狀態轉換。
這樣,客車只是一直呼叫其當前狀態碼對應的狀態指標來執行邏輯,(狀態類物件指標的函式悄悄地改變了客車類中的當前狀態碼,這樣,在客車不知不覺地過程中,完成了狀態的轉換)

這裡寫圖片描述

    class Vehicle
    {
    public:
        Vehicle()
        {
            _brand = "Volvo" ;
            _identifier = 1 ;
            _speed = 2 ;
            _driveDirect = FORWARD ;
            _curStateNo = 0 ;
            _curPos = 0 ;
        }

        int Init(const Time& curTime) ;

        //run
        int Running(const Time& curTime) ;

        //根據當前時間,返回vehicle當前狀態:起點start、路上running、中間站停車midStop、終點endStop
        int GetVehicleState(const Time& curTime) ;

    private:
        std::string   _brand ;             //Vehicle品牌(名字)
        int           _identifier ;        //Vehicle的編號(不同品牌分別編號)
        double        _speed ;             //車速(單位為:公里/分鐘)
        int           _passengerNumLimit ; //載客量
        int           _curStateNo ;        //當前Vehicle所處狀態碼
        DirectKind    _driveDirect ;       //當前Vehicle的行駛方向
        int           _curPos ;            //當前位置(離始發站的距離)

        //每個Vehicle都有一張狀態碼和狀態物件對映表,我們在Vehicle初始化的時候建立所有狀態物件
        std::map<int, VehicleState*> _vehicleStateMap ;
        //Vehicle執行時間表(每一站的到達時間和發車時間)
        std::vector<std::pair<Time, std::string> > _vehicleSchedule ;

        //改變當前狀態
        VehicleState* ChangeState(int destStateNo) ;
        //計算執行時刻表
        int CalcVehicleSchedule(const Time& startTime, const DirectKind& driveDirect) ;

        friend class VehicleState ;
    } ;

客車對外的介面只有running();
而running所做的工作只是呼叫當前客車狀態指標的process函式,並把自己和當前時間作為引數傳入。
把主要的邏輯和處理交給客車狀態物件去做。

    int Vehicle::Running(const Time& curTime)
    {
        int ret ;
        ret = _vehicleStateMap[_curStateNo]->Process(this, curTime);

        if (ret == -1)
            return -1 ;
        return 0 ;
    }
    //客車狀態類
    //交通工具介面(抽象類)
    class VehicleState
    {
    public:
        VehicleState() {} 

        virtual int Process(Vehicle* pVehicle, const Time& curTime) = 0 ;
    protected:
        int ChangeState(Vehicle* pVehicle , int destStateNo);
    } ;

    //啟動狀態
    class StartState : public VehicleState
    {
    public:
        int Process(Vehicle* pVehicle, const Time& curTime) ;
    } ;

    //行駛狀態
    class RunningState : public VehicleState
    {
    public:
        int Process(Vehicle* pVehicle, const Time& curTime) ;

    } ;

    //中途停車狀態
    class MidStopState : public VehicleState
    {
    public:
        int Process(Vehicle* pVehicle, const Time& curTime) ;

    } ;

    //到站停車狀態
    class EndStopState : public VehicleState
    {
    public:
        int Process(Vehicle* pVehicle, const Time& curTime) ;

    } ;

在狀態類的process函式中,所做的工作是:1、處理當前狀態下的事情。2、根據邏輯改變客車的當前狀態(所以,狀態類是客車類的友元)

    int RunningState::Process(Vehicle* pVehicle, const Time& curTime)
    {
        std::cout << "Run\n" ; //在當前執行狀態下,我們僅僅代表性地輸出Run。

        //先判斷當前情況下能否行車(是否到站,根據時間判斷:初始發車時 車會獲得一個發車時間和和乘客資訊,此時計算執行時刻表,每次啟動的時候都要計算)
        Time nextTime = curTime ;
        nextTime.AddTime(1) ;
        int nextVehicleState = 0 ;
        nextVehicleState = pVehicle->GetVehicleState(nextTime) ;

        //轉換到下一個狀態(根據時間判斷是否:中途停車、終點停車、在路上)
        if (nextVehicleState == -1)
        {
            return -1 ;
        }
        if (nextVehicleState == MIDSTOP)
        {
            ChangeState(pVehicle,MIDSTOP) ;
        }
        else if (nextVehicleState == ENDSTOP)
        {
            ChangeState(pVehicle,ENDSTOP) ;
        }

        return 0 ;
    }

我們把主要的邏輯寫在狀態類中,且狀態的轉化也是在狀態類中完成的,客車類並不知道。

這樣,在外部迴圈中,我們只需要呼叫客車的running函式且把時間傳入即可,其中的執行和狀態轉化會自動進行。

狀態模式

使用狀態模式前,客戶端外界需要介入改變狀態,而狀態改變的實現是瑣碎或複雜的。
使用狀態模式後,客戶端外界可以直接使用事件Event實現,根本不必關心該事件導致如何狀態變化,這些是由狀態機等內部實現。

這是一種Event-condition-State,狀態模式封裝了condition-State部分。
每個狀態形成一個子類,每個狀態只關心它的下一個可能狀態,從而無形中形成了狀態轉換的規則。如果新的狀態加入,只涉及它的前一個狀態修改和定義。
狀態轉換有幾個方法實現:一個在每個狀態實現next(),指定下一個狀態(本文中就是使用這種方法);還有一種方法,設定一個StateOwner,在StateOwner設定stateEnter狀態進入和stateExit狀態退出行為。
狀態從一個方面說明了流程,流程是隨時間而改變,狀態是擷取流程某個時間片。
關於狀態機的一個極度確切的描述是它是一個有向圖形,由一組節點和一組相應的轉移函式組成。狀態機通過響應一系列事件而“執行”。每個事件都在屬於“當前” 節點的轉移函式的控制範圍內,其中函式的範圍是節點的一個子集。函式返回“下一個”(也許是同一個)節點。這些節點中至少有一個必須是終態。當到達終態, 狀態機停止。

使用情景

State模式在實際使用中比較多,適合”狀態的切換”.因為我們經常會使用If elseif else 進行狀態切換, 如果針對狀態的這樣判斷切換反覆出現,我們就要聯想到是否可以採取State模式了.
不只是根據狀態,也有根據屬性.如果某個物件的屬性不同,物件的行為就不一樣,這點在資料庫系統中出現頻率比較高,我們經常會在一個數據表的尾部,加上property屬性含義的欄位,用以標識記錄中一些特殊性質的記錄,這種屬性的改變(切換)又是隨時可能發生的,就有可能要使用State.

【注意:若是根據不同的條件有不同的處理,這種if-else不必用狀態模式,直接用表驅動即可,用查表的方式設計更合理】

在實際使用,類似開關一樣的狀態切換是很多的,但有時並不是那麼明顯,取決於你的經驗和對系統的理解深度.
這裡要闡述的是”開關切換狀態” 和” 一般的狀態判斷”是有一些區別的, ” 一般的狀態判斷”也是有 if..elseif結構,例如:

    if (which==1) state="hello";
    else if (which==2) state="hi";
    else if (which==3) state="bye";

這是一個 ” 一般的狀態判斷”,state值的不同是根據which變數來決定的,which和state沒有關係.
如果改成:

    if (state.euqals("bye")) state="hello";
  else if (state.euqals("hello")) state="hi";
  else if (state.euqals("hi")) state="bye";

這就是 “開關切換狀態”,是將state的狀態從”hello”切換到”hi”,再切換到”“bye”;在切換到”hello”,好象一個旋轉開關,這種狀態改變就可以使用State模式了.
如果單純有上面一種將”hello”–>”hi”–>”bye”–>”hello”這一個方向切換,也不一定需要使用State模式,因為State模式會建立很多子類,複雜化,但是如果又發生另外一個行為:將上面的切換方向反過來切換,或者需要任意切換,就需要State了.

多執行緒

剛才我們解決了一個核心問題,讓客車動起來。現在我們要實現的是同時讓多輛客車行駛起來。
我們可以用序列的方式來模擬這個過程:用同一時刻時間值來遍歷所有的客車,激發客車的執行,模擬出在某時刻多輛客車執行的效果。
我們用多執行緒的方式來模擬這一過程,每一輛客車的執行由一個執行緒負責,在某時刻客車執行緒同時執行。

這裡寫圖片描述

本專案中,使用的是Unix下的執行緒同步機制——條件變數,關於條件變數

條件變數(cond)

當我們遇到期待的條件尚未準備好時,我們應該怎麼做?我們可以一次次的迴圈判斷條件是否成立,每次給互斥鎖解鎖又上鎖。這稱為輪詢(polling),是一種對CPU時間的浪費。
我們也許可以睡眠很短的一段時間,但是不知道該睡眠多久。
我們所需的是另一種型別的同步,它允許一個執行緒(或程序)睡眠到發生某個時間為止。

互斥量用於上鎖,條件變數則用於等待。則兩種不同型別的同步都是需要的。

條件變數是與互斥量一起使用的,因為條件本身是由互斥量保護的,執行緒在改變條件狀態前必須首先鎖住互斥量。

  • API
    int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t mutex) ;
    使用pthread_cond_wait等待條件變為真,傳遞給pthread_cond_wait的互斥量對條件進行保護,呼叫者把鎖住的互斥量傳給函式。函式把呼叫執行緒放到等待條件的執行緒列表上,然後對互斥量解鎖。pthread_cond_wait返回時,互斥量再次被鎖住。

  • 示範程式碼:

pthread_mutex_lock(&var.mutex) ;
while (條件為假)
     {pthread_cond_wait(&var.cond, &var.mutex) ;}
修改條件
pthread_mutex_unlock(&var.mutex) ;

通知執行緒條件已滿足:
int pthread_cond_signal (pthread_cond_t* cond) ;
//喚醒等待條件的某個執行緒

int pthread_cond_broadcast (pthread_cond_t* cond) ;
//喚醒等待該條件的所有執行緒

【程式碼示例】

struct
{
    pthread_cond_t cond ;
    pthread_mutex_t mutex ;
    int continueRun ;
} oneready = {
    PTHREAD_COND_INITIALIZER ,
    PTHREAD_MUTEX_INITIALIZER,
    0
} ;

struct
{
    pthread_cond_t cond ;
    pthread_mutex_t mutex ;
    int pthreadNum ;
} allready = {
    PTHREAD_COND_INITIALIZER ,
    PTHREAD_MUTEX_INITIALIZER,
    0
} ;

Time            g_curTime ;
int             g_curBusNum = 0 ;
pthread_mutex_t mutexTime   = PTHREAD_MUTEX_INITIALIZER ; 
pthread_mutex_t mutexBusNum = PTHREAD_MUTEX_INITIALIZER ;
//主執行緒
for (int i=0; i<130; ++i) { //130只模擬130分鐘,此是為了示範而寫
        startStation.Run(g_curTime) ;//會根據時間表來生成客車執行緒

        //等待所有執行緒完成一輪工作(若當前無執行緒則跳過)
        pthread_mutex_lock(&allready.mutex) ;
        while(allready.pthreadNum != -g_curBusNum)
        {
            //若所有的執行緒都銷燬了,則本執行緒不能繼續阻塞等待
            pthread_mutex_lock(&mutexBusNum) ;
            bool allEnded = (g_curBusNum == 0) ;
            pthread_mutex_unlock(&mutexBusNum) ;
            if (allEnded)
                break ;

            pthread_cond_wait(&allready.cond, &allready.mutex) ;
        }
        allready.pthreadNum = 0 ; 
        pthread_mutex_unlock(&allready.mutex) ;

        //時間增加1
        pthread_mutex_lock(&mutexTime) ;
        g_curTime.AddTime(1) ;
        pthread_mutex_unlock(&mutexTime) ;

        //通知所有執行緒繼續
        if (g_curBusNum > 0)
        {
            pthread_mutex_lock(&oneready.mutex) ;
            oneready.continueRun = 1 ;
            pthread_mutex_unlock(&oneready.mutex) ;
            pthread_cond_broadcast(&oneready.cond) ;
        }
    }
//客車執行緒
void* busrun(void* busArgv)
{
    while (1) {
        //做自己的事情
        Vehicle* pBusArgv = (Vehicle*)busArgv ;
        pthread_mutex_lock(&mutexTime) ;
        g_curTime.Show(std::cout) ;
        pthread_mutex_unlock(&mutexTime) ;

        int retState = 0 ;
        retState = pBusArgv->Running(g_curTime) ;

        //若自己是最後一個完成的,則通知主控制執行緒
        pthread_mutex_lock(&allready.mutex) ;
        allready.pthreadNum-- ;
        if (allready.pthreadNum == -g_curBusNum) {
            if (retState == -1) //bus跑完全程,回收
            {
                pthread_mutex_lock(&mutexBusNum) ;
                g_curBusNum-- ;
                pthread_mutex_unlock(&mutexBusNum) ;
            }

            pthread_cond_signal(&allready.cond) ;
        }
        pthread_mutex_unlock(&allready.mutex) ;

        //bus跑完全程,此執行緒結束
        if (retState == -1)
            break;

        //等待可以繼續執行的訊號
        pthread_mutex_lock(&oneready.mutex) ;
        while(oneready.continueRun == 0)
        {
            pthread_cond_wait(&oneready.cond, &oneready.mutex) ;
        }
        oneready.continueRun = 0 ;
        pthread_mutex_unlock(&oneready.mutex) ;
    }

    return NULL ;
}

startStation.Run(g_curTime) ;//根據當前時間判斷是否到了發車時間,若到了發車時間,則生成一個客車執行緒。

至於乘客上下車,車站對客車的排程,實現不難,有興趣的朋友可以自己用C++實現全部功能。

相關推薦

一個簡單專案學習面向物件程式設計(設計模式執行)

下面的專案是兩年前學校老師佈置的一個小專案,當時自己用了一種很笨拙的方式實現了,現在用面向物件的思想和多執行緒重構這個專案。 問題描述: 西寶高速模擬模擬 西安市到寶雞市之間是我省主要的高速公路客運路線之一,經過簡化後的客運路線端點、中途停靠點和里程如下圖

PHP面向物件程式設計設計模式(一)策略模式

(一)什麼是面向物件程式設計   面向物件(OO)的定義是什麼,在面向物件的入門課程C++(或者JAVA)中,封裝資料和方法好像是面向物件最重要的一個特點,當然還有基於繼承實現的多型和過載。其實每一種OOP語言,由於彼此功能上的差異性,這些特點只能適用於某一種

設計模式執行——用命令模式設計執行架構

下載原始碼   圖一 圖二 毫無疑問,多執行緒會增加寫程式碼的難度。在有併發處理的情況下,debug變得更困難,程式碼中的臨界區必須得到保護,共享的資源也得管理好。當然,通過增加程式執行的執行緒數帶來的結果是:效率可以大大提高。從一個程式設計師的角度來說,處理執行緒是

Java學習-面向物件程式設計的三大特性(型)

文章目錄 一、基本含義 1.1 基本點 1.2 注意事項 1.2.1 多型使用——系統呼叫步驟 1.2.2 父類引用指向子類物件 1.3 多型的實現方式 1.3.1 方式一——重

javascript面向物件程式設計--設計超類子類,設計元類

在javascript中,Object物件是通用類,其他所有內建物件和自定義構造物件都是專用類,即Object物件是超類,其他內建物件和自定義物件都是Object的子類,所有在javascript語言中,所有的物件都繼承Object定義的屬性和方法 Object.prototype.name='

typeScript(7)--ts面向物件程式設計,繼承重寫

類的繼承 在使用TypeScript這門語言時,一個最重要基本功就是面向物件程式設計,那對類的擴充套件就變的格外重要,擴充套件經常使用的手段就是繼承。 繼承:允許我們建立一個類(子類),從已有的類(父類)上繼承所有的屬性和方法,子類可以新建父類中沒有的屬性和方法。

C++Primer_Chap15_面向物件程式設計_List08_容器繼承_筆記

  當我們使用容器存放繼承體系中的物件時,通常必須採用間接儲存的方式。因為不允許在容器中儲存不同型別的元素,所以不能把具有繼承關係的多種型別的物件之間存放在容器中。 在容器中放置(智慧)指標而非物件 vector<shared_ptr<Quote>> bas

python 面向物件程式設計:類例項

深度學習在構建網路模型時,看到用類來構建一個模型例項,清晰明瞭,所以這篇博文主要學習一下python類 類和例項: 類可以起到模板的作用,因此,可以在建立例項的時候,把一些我們認為必須繫結的屬性強制填寫進去。通過定義一個特殊的__init__(注意:特殊方法“__init__”前後分別有

初探PHP面向物件設計模式-策略模式

1. 什麼是策略模式 簡單的講就是實現一個問題的多種方法就是策略設計模式,我們在開發微信公眾號時,有一組被動接收微信訊息的介面(例如:普通文字訊息、關注事件訊息、取消關注事件訊息……),針對不同的訊息有多種處理方式,有處理文字有處理關注事件的等等我們使用的邏輯演算法都不一樣,當然啦業

1.面向過程程式設計 2.面向物件程式設計 3.類物件 4.python 建立類物件 如何使用物件 5.屬性的查詢順序 6.初始化函式 7.繫結方法 與非繫結方法

1.面向過程程式設計 面向過程:一種程式設計思想在編寫程式碼時 要時刻想著過程這個兩個字過程指的是什麼? 解決問題的步驟 流程,即第一步幹什麼 第二步幹什麼,其目的是將一個複雜的問題,拆分為若干的小的問題,按照步驟一一解決,也可以說 將一個複雜的問題,流程化(為其制定一個固定的實現流程),從而變得簡單化例如

java8實戰-使用Lambda重構面向物件設計模式

策略模式 //面向物件模式 public interface ValidationStrategy { boolean execute(String s); } public class IsAllLowerCase implements ValidationStr

面向物件程式設計-私有屬性私有方法

1.私有屬性 1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 # Author:James Tao 4 class Role(object):#執行時之後就存在記憶體裡 5 6 #建構函式 7 #作用:在例項化時做一

第26天面向物件程式設計之組合,型,封裝

組合 人生三問: 什麼是組合  組合就是將一個物件A定義為另一個物件B的屬性。從而使得B不僅能夠訪問自己的屬性,還會具備A的屬性,就像是把物件A和物件B組合到一塊一樣。 為什麼要用組合  和繼承一樣為了減少類與類之間的程式碼冗餘。  問題來了既然已經有了繼承,為什麼還要有組合呢?主要是為了解決一些沒有父子關

Java初學 面向物件程式設計(介面內部類)

Java初學 面向物件程式設計(介面和內部類) 1、定義一個Phone介面,其中包含String GetPrice()方法和double GetWeight()方法;(1)在主類中設計void PrintPhone(Phone p)方法,呼叫Phone介面中的兩

Java-面向物件程式設計-三大特性之

我們前面已經介紹了面向物件程式設計的三大特性之二,今天就介紹最後一個特性-多型。 什麼叫多型?從字面上理解就是多種形態,即對同一個客體,可以有多種不同的形式。就好像糖一樣,有多種口味,你想吃什麼口味的就可以吃什麼口味。但在程式中,卻不是你想要怎樣就怎樣。更多的

自學Python day6--------面向物件程式設計(類例項)

自學Python day6——–面向物件程式設計(類和例項) 1.類和例項 面向物件最重要的概念就是類(Class)和例項(Instance),必須牢記類是抽象的模板,比如Student類,而例項是根據類創建出來的一個個具體的“物件”,每個物件都擁有相同的方

面向物件程式設計_類物件的定義及使用_單選題

設A為自定義類,現有普通函式int fun(A& x)。則在該函式被呼叫時(D): (2分) 將執行復制建構函式來初始化形參x僅在實參為常量時,才會執行復制建構函式以初始化形參x無需初始化形參x僅在該函式為A類的友元函式時,無需初始化形參x

面向物件設計模式(一)

一、設計模式的概念 什麼是好的軟體設計?—— 複用! ◆  首先我們要先理解一個問題,什麼是模式? 每一個模式描述了一個在我們周圍不斷重複發生的問題,以及該問題的解決方案的核心。這樣,你就能一次又一次地使用該方案而不必做重複勞動。  ◆  設計模式:也就是為了使程式碼的可複

Python面向物件程式設計(類例項 訪問限制 繼承型 獲取物件資訊 例項屬性類屬性)

面向物件程式設計——Object Oriented Programming,簡稱OOP,是一種程式設計思想。OOP把物件作為程式的基本單元,一個物件包含了資料和操作資料的函式。 資料封裝、繼承和多型是面向物件的三大特點 在Python中,所有資料型別都可以視

增加一個間接層解耦的所有設計模式總結

增加一個間接層來解耦的設計模式有: 工廠方法模式 抽象工廠模式 模板方法模式 建造者模式 橋樑模式 命令模式 直譯器模式工廠方法模式          工廠方法(Factory Method)模式的意義是定義一個建立產品物件的工廠介面,將實際建立工作推遲到子類當中。 那麼工