Symbian中所體現的軟件編程藝術

Author:孫東風 2007-04-08

MVC架構

http://blog.csdn.net/dongfengsun/article/details/1556704

        我們知道,在軟件編寫過程中一直提倡"數據"和"界面"的高度分離,Symbian中也是這麼做的。

        首先,基於"傳統EIKON框架"的應用程序會產生App、Document、AppUi、Container四個類,其中App是應用程序的"啓動類",Document基礎上沒什麼用處,而Symbian中大量的處理工作都放在了AppUi和Container類中。AppUi就象是一個交通樞紐負責南來北往的數據,一般來說,在Symbian的程序中都會新建一個Engine的"引擎類"來負責程序的邏輯處理,而AppUi就是負責把"引擎類"中數據的處理結果、數據的變化及時更新到Container中。

        下面是我寫的一個Symbian遊戲引擎中AppUi二階段構造函數中的代碼:

void CMegajoyAppUi::ConstructL()
    {
    BaseConstructL();

    iAppContainer = new (ELeave) CMegajoyContainer;
   iAppContainer->SetMopParent( this );
   iAppContainer->ConstructL( ClientRect() );
   AddToStackL( iAppContainer );

iMainEngine = new (ELeave) CMegajoyMain(this);

iLancher = CIdle::NewL( CActive::EPriorityIdle );

iLancher->Start(TCallBack(Start,this));

}

        從中可以看到,上面AppUi的二階段構造函數中同時產生了iAppContainer和iMainEngine實例,並且我們把一個AppUi的this指針傳遞給了"引擎類"。我們知道,做爲一種GUI程序,無非就是用戶界面的交互(包括鍵盤、鼠標等)和引擎處理數據。而Symbian中提供給用戶界面交互接口的正是AppUi類,它裏面的HandleCommandL()負責處理用戶的菜單操作,HandleKeyEventL()負責處理用戶的鍵盤操作,DoExit()負責用戶的退出操作等。那麼,一切數據的處理和界面的顯示都經過AppUi這個中樞就顯得很有必要了!

        試想一下,用戶按下某個鍵,這個鍵從傳遞到AppUi的HandleKeyEventL()函數裏(當然也有可能是其上某個控件的消息響應函數中,這裏忽略控件棧的討論),而AppUi調用iMainEngine來處理這個按鍵數據,從而進行必要的邏輯轉變,比如從一個界面跳轉到另一個界面,那麼iMainEngine裏就會把一個全局的界面ID從一種狀態轉換到另一種狀態,但是這種狀態的切換會伴隨着界面的變化,界面上也需要體現出這種變化,而界面的繪製是在iAppContainer中完成的,iAppContainer又是在AppUi中構造並初始化的。

        就是說我們的iAppContainer和iMainEngine需要一種類似通信的機制,讓iAppContainer能及時的知道iMainEngine中某個全局變量狀態的變化並及時做出界面上的更新。

        問題到了這裏已經很明顯,iAppContainer和iMainEngine都在AppUi類裏,而這兩個實例對象之間需要一種類似通信的機制。

        解決這種兩個對象實例之間通信問題的非我們的"Observer模式"莫屬!下面我們就來看看Symbian中的"Observer模式"。

Observer模式

        Observer模式提供一種類與類之間傳遞消息的機制,當某個事件發生或狀態改變時,擁有觀察者的類可以向另一個類發送某個消息,這樣另一個類可以根據變化做出相應的處理。呵呵,是不是覺得簡直是爲了我們Symbian量身定做的一個模式?

       在我們的Symbian架構中iMainEngine中會有事件發生(因爲AppUi類把事件傳遞給它了)或狀態改變(例如全局界面ID的變化),那麼我們的iMainEngine中就需要有一個"觀察者"的實例以便向iAppContainer發送消息,iAppContainer可以根據變化做出相應的處理。

        這樣我們就可以定義一個消息接口的類,這個類是抽象的,內部的函數也都是純虛函數(只提供接口),下面是我寫的Symbian遊戲引擎中觀察者接口(或者說消息接口)的定義:

class MMegajoyAction{
public:
 // Graphic functions
 virtual void FlipBackBuffer(void)=0;
 virtual void BitBlt(TInt iBltType, CExtendedBitmap &iBitmap, const TPoint &aPosition)=0;
 virtual void DoExit(void)=0;
 virtual TBool ReadIniFile(TUid iInfo, void *ptr, TUint &size)=0;
 virtual TBool CheckIniFile(TUid iInfo)=0;
 virtual void WriteIniFile(TUid iInfo, void *ptr, TUint size)=0;
 virtual void RemoveIniData(TUid iInfo)=0;
};

        因爲iMainEngine和iAppContainer都在AppUi中,那麼AppUi就成爲它們之間消息中轉的最佳選擇了,讓AppUi類實現這個接口MMegajoyAction,並傳遞AppUi的this指針到iMainEngine,而iMainEngine中也有個MMegajoyAction的實例對象 MMegajoyAction *Actions;從而當iMainEngine中有事件發生(因爲AppUi類把事件傳遞給它了)或狀態改變(例如全局界面ID的變化)時直接調用Actions傳遞消息到AppUi,AppUi中通過實現的具體接口再調用iAppContainer中的方法,實現了iMainEngine和iAppContainer之間的通信機制。

         如下是我寫的Symbian遊戲引擎中AppUi對觀察者接口的實現

void CMegajoyAppUi::FlipBackBuffer(void){
 iAppContainer->FlipBackBuffer();
}

void CMegajoyAppUi::BitBlt(TInt iBltType, CExtendedBitmap &iBitmap, const TPoint &aPosition){
 switch(iBltType){
 case BLTTYPE_NORMAL:
  iAppContainer->BlitToBackBuffer(iBitmap, aPosition);
  break;
 case BLTTYPE_MASKED:
  iAppContainer->BlitToBackBufferMasked(iBitmap, aPosition);
  break;
 }
}

void CMegajoyAppUi::DoExit(void){
 Exit();
}

TBool CMegajoyAppUi::ReadIniFile(TUid iInfo, void *ptr, TUint &size){
 TInt r;
 TBool result = EFalse;
 RFs fs;
 fs.Connect();
 CleanupClosePushL( fs );
 RDictionaryReadStream rdsIniFile;
 CDictionaryStore *cdIniFile = Application()->OpenIniFileLC(fs);
 if (cdIniFile->IsPresentL(iInfo)){
  rdsIniFile.OpenLC(*cdIniFile, iInfo);
  TPtr8 buf((TUint8*)ptr, size);
  TRAP(r, rdsIniFile.ReadL(buf));
  CleanupStack::PopAndDestroy(); // rdsIniFile
  result = ETrue;
 }
 CleanupStack::PopAndDestroy( 2 ); // fs, cdIniFile
 return result;
}

TBool CMegajoyAppUi::CheckIniFile(TUid iInfo){
 TBool result = EFalse;
 RFs fs;
 fs.Connect();
 CleanupClosePushL( fs );
 RDictionaryReadStream rdsIniFile;
 CDictionaryStore *cdIniFile = Application()->OpenIniFileLC(fs);
 result = cdIniFile->IsPresentL(iInfo);
 CleanupStack::PopAndDestroy( 2 ); // fs, cdIniFile
 return result;
}

void CMegajoyAppUi::WriteIniFile(TUid iInfo, void *ptr, TUint size){
 TInt r;
 RFs fs;
 fs.Connect();
 CleanupClosePushL( fs );
 RDictionaryWriteStream rdsIniFile;
 CDictionaryStore *cdIniFile = Application()->OpenIniFileLC(fs);
 rdsIniFile.AssignLC(*cdIniFile, iInfo);
 TPtr8 buf((TUint8*)ptr, size, size);
 TRAP(r, rdsIniFile.WriteL(buf));
 rdsIniFile.CommitL();
 CleanupStack::PopAndDestroy();
 cdIniFile->CommitL();
 CleanupStack::PopAndDestroy( 2 );
}

void CMegajoyAppUi::RemoveIniData(TUid iInfo){
 RFs fs;
 fs.Connect();
 CleanupClosePushL( fs );
 RDictionaryWriteStream rdsIniFile;
 CDictionaryStore *cdIniFile = Application()->OpenIniFileLC(fs);
 cdIniFile->Remove(iInfo);
 cdIniFile->CommitL();
 CleanupStack::PopAndDestroy( 2 );
}

         可見,接口的實現也分爲兩部分

virtual void FlipBackBuffer(void)=0;
virtual void BitBlt(TInt iBltType, CExtendedBitmap &iBitmap, const TPoint &aPosition)=0;

        這兩個接口屬於界面的更新,AppUi直接調用iAppContainer中的函數進行消息的傳遞,而其它幾個數據保存、讀取、刪除的操作都是在AppUi本地完成。

        而下面的用戶接口消息(按鍵、菜單等操作)則直接傳遞消息給iMainEngine進行處理:

TKeyResponse CMegajoyAppUi::HandleKeyEventL(const TKeyEvent& aKeyEvent,TEventCode aType) {
  if(iMainEngine)
   return iMainEngine->DoKeyEvent(aKeyEvent, aType);
  return EKeyWasNotConsumed;
}
void CMegajoyAppUi::HandleCommandL(TInt aCommand) {
 switch ( aCommand ) {
 case EAknSoftkeyOk:
  iMainEngine->ExternalEvent(EVT_SELECT);
  break;
 case EAknSoftkeyBack:
  iMainEngine->ExternalEvent(EVT_ESCAPE);
  break;
 case EEikCmdExit:
  iMainEngine->ForceQuit();
  Exit();
  break;
 default:
  break;     
 }
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章