以一個簡單的專案來學習面向物件程式設計(設計模式和多執行緒)
下面的專案是兩年前學校老師佈置的一個小專案,當時自己用了一種很笨拙的方式實現了,現在用面向物件的思想和多執行緒重構這個專案。
問題描述:
西寶高速模擬模擬
西安市到寶雞市之間是我省主要的高速公路客運路線之一,經過簡化後的客運路線端點、中途停靠點和里程如下圖所示(括號裡是簡稱,里程的單位是公里):
限定條件
(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)模式的意義是定義一個建立產品物件的工廠介面,將實際建立工作推遲到子類當中。 那麼工