程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 設計模式之State學習心得

設計模式之State學習心得

編輯:關於C語言

 

        最近學習了設計模式的State模式,因為曾碰到過HTML的解析問題,正好需要使用狀態機來分析,所以就嘗試了使用State模式來寫HTML的解析過程,雖然這有點殺雞用牛刀的味道,但對設計模式的理解也有不少的收獲,在此和大家分享一下。   一、HTML解析分析         HTML解析基本上是要對每個字符進行判斷,字符內容大概可以分以下幾類:   : < : > : / : = : 字母 : 數字 : 下劃線 : 漢字   另外還有用於注釋的<!--、-->,這裡暫不處理。 對於HTML的解析可以用狀態圖來分析如下:   圖1)     從上圖可以看出,HTML的狀態基本上是處理標簽名、標簽值和標簽屬性這三大類狀態,如果再細分的話,標簽名還可以再分為左標簽名和右標簽名,屬性還可以再分為屬性名和屬性值,當然還有一些情況,如注釋和一些錯誤的數據,這些細節就暫不考慮。 如果這裡單單用一個switch的分支處理的話,代碼就會很復雜,起碼要用兩層switch,第一層用於判斷當前讀取的字符,第二層需要判斷當前的狀態。偽代碼如下:  
  1.        do {  
  2.               char ch = *itChar;  
  3.               switch(ch)  
  4.               {  
  5.               case '<':  
  6.                      switch(State)  
  7. {  
  8. case 開始狀態:  
  9.        開始處理…  
  10.        切換到左標簽狀態  
  11. break;  
  12. case 標簽值狀態:  
  13.       標簽值狀態處理…  
  14.       break;  
  15. }  
  16.                      break;  
  17.               case '>':  
  18.                      switch(State)  
  19. {  
  20. case 左標簽名狀態:  
  21.         左標簽名狀態處理…  
  22.                             切換到標簽名狀態  
  23.                             break;  
  24.                      case 右標簽名狀態:  
  25.                             右標簽名狀態處理…  
  26.                             break;  
  27. }  
  28. break;  
  29. …  
  30. }  
  31. } while(字符串讀取未結束)  
 
 
    第二層的每個分支都可以寫成一個函數,如果有狀態增加的話,那麼就要增加case的分支處理。 是否有更好的方法可以不用switch語句呢?當然你會說,可以使用循環處理,照個思路的話,那就要為循環處理建立一張映射表,用於進行狀態的切換。如果不用映射表呢,有什麼好的辦法麼。   二、state模式         設計模式中的State模式就可以很好的解決這個問題,當然這裡面就要用到很多的類。我們先看下面的關系圖

 

圖2)     首先解釋下上圖,這裡起碼要建立三個類,Context、State、和ConcreteStateX: Context是負責執行狀態處理的,它包含了一個State*的基類指針,當每次進行狀態處理時,就調用Request,而在Request中會通過State*這個基類指針調用ConcreteStateA或ConcreteStateB對象的Handle()。我們可以把一種狀態寫成一個ConcreteStateX對象,然後把處理放在Handle裡面,這樣只要循環調用Request()就可以完成所有狀態處理。這時,你也許就要問了,狀態和狀態直接的切換如何實現呢?關鍵就在這裡,因為在Context裡有個State*,我們只需要更改State*所指的對象就可以了。那麼什麼時候更改這個指針呢,我們可以把這個處理放在Handle裡面,當我們需要切換狀態時,調用State中的ChangeState(…, state : State *)方法,通過參數將State對象傳入參見代碼),然後更改Context中的State*指針,這裡需要注意的是,在Context裡面需要將State聲明為firend,這樣做的好處就是可以直接訪問Context中的私有字段,而將其他的類拒之門外。我們在Context裡面還加了個方法ChangeState,當然也可以直接賦值,不調用這個方法,但這樣寫有個好處,我們待會再講。         講到這裡,你也許會問了,一個狀態就要用一個類,那麼狀態多了就要創建很多個類對象,這麼多個類對象如何管理呢,一個比較簡單的方法就是將每個狀態類用單例實現,這樣就不需要關心對象的創建和釋放。還有一種方法是使用工廠模式,將這些類集中創建,集中釋放。但是你是否考慮到釋放的環節呢,在什麼地方釋放是最合適的。對了,就在我們剛才提到的ChangeState中,當我們重新賦值state時,就可以把前面的一個State對象給delete掉,如果是單例,就調用Release()方法,這樣對需要使用大量內存的狀態對象時是很有好處的。        如果你明白以上的內容,那麼下面的代碼你就很容易能看明白了: //state.h #ifndef _STATE_H_ #define _STATE_H_ class Context; //前置聲明 class State { public:        State();        virtual ~State();        virtual void Handle(Context* ) = 0; protected:        bool ChangeState(Context* con,State* st); }; class ConcreteStateA:public State { public:        ConcreteStateA() {};        virtual ~ConcreteStateA() {};        virtual void Handle(Context* ); }; class ConcreteStateB:public State { public:        ConcreteStateB() {};        virtual ~ConcreteStateB() {};        virtual void Handle(Context* ); }; #endif //~_STATE_H_     //state.cpp #include "Context.h" #include <iostream> using namespace std; void State::Handle(Context* con) {        cout<<"State::.."<<endl; } bool State::ChangeState(Context* con,State* st) {        con->ChangeState(st);        return true; } void ConcreteStateA::Handle(Context* con) {        cout<<"ConcreteStateA::OperationInterface......"<<endl;        this->ChangeState(con,new ConcreteStateB());  } void ConcreteStateB::Handle(Context* con) {        cout<<"ConcreteStateB::OperationInterface......"<<endl;        this->ChangeState(con,new ConcreteStateA());  }   //context.h #ifndef _CONTEXT_H_ #define _CONTEXT_H_ class State; class Context { public:        Context();        Context(State* state);        ~Context();        void Request(); protected: private:        friend class State; //表明在State類中可以訪問Context類的private字段        bool ChangeState(State* state); private:        State* _state; }; #endif //~_CONTEXT_H_   //context.cpp #include "stdafx.h" #include "Context.h" #include "State.h" Context::Context() { } Context::Context(State* state) {        this->_state = state; } Context::~Context() {        delete _state; } void Context::Request() {        _state->Handle(this); } bool Context::ChangeState(State* state) {        if (_state) delete _state;        _state = state;        return true; }   //測試代碼 int StateTest() {        State* st = new ConcreteStateA();        Context* con = new Context(st);        con->Request();        con->Request();        con->Request();        if (con != NULL)               delete con;        if (st != NULL)               st = NULL;        return 0; }          故事講到這裡,還沒有結束呢,從圖1)我們可以看到,每個狀態常會帶個Entry處理和Exit處理,在進入狀態和離開狀態時,我們經常會需要進行一些處理,因為每個狀態會有重復狀態的處理,我們把重復性的處理單獨分離出來。具體實現的時候,有三種方案: 一、我們可以在調用Request之前調用Entry方法(),並且加上一個狀態判斷,檢查是否是同一個狀態,這樣就需要加個額外的變量記錄當前的狀態。Exit()方法則可以加在State::ChangeState()的方法中在調用Context::ChangState()之前調用,這樣就可以了。 二、將每個狀態類用單例實現,在類的構造中調用Entry(),在析構中調用Exit(),這樣在ChangeState中調用對象的Release()即可調用Exit(),Entry可以在對象的Instance()時被調用,這種方法遺留了一個缺陷,下個狀態的Entry會在上個狀態的Exit()前被調用。 三、可以通過使用工廠模式,在類的構造中調用Entry(),在析構中調用Exit(),當然這就需要在ChangeState中調用delete和new了,既然使用了工廠模式,那麼在調用ChangeState的傳入的參數就不需要使用對象了,而是可以是狀態ID,在Constext::ChangeState裡,先delete掉前一個狀態對象,然後再調用new創建新的對象,這樣代碼就比較完美了。     State模式的應用心得就講到這裡了,HTML的代碼如何實現就不是這麼簡單了,而解析過程只是為了顯示HTML服務,這只是實現的一部分,而真正要實現HTML的浏覽難就更復雜了,使用棧處理可能會高效一些,這裡我們只是將其作為State的一個應用來描述。設計模式裡的各個模式都不是孤立的,需要針對具體情況搭配的使用,在一個代碼裡可能會用到若干種模式,善用這些對提高代碼的質量和可復用性很有好處。您如果有什麼更好的建議和想法,請不吝賜教,或對我的描述有什麼疑問或錯誤的,也請慷慨指出。

 

本文出自 “飄~~~” 博客,請務必保留此出處http://xulin.blog.51cto.com/264387/410565

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved