2009年10月12日

          1.1 BUILDER生成器

          1、 意圖

          將一個復雜對象的構建和它的表示分離,使得同樣的構建過程可以創建不同的表示。

          構建是通過對生成器抽象接口的調用實現的。而構建出來的產品的類型(表示),是由具體的進行構建的生成器的子類的實例來決定的。這樣導向器只需要調用生成器的抽象接口生成產品,而具體生成什么樣的產品,則是根據導向器配置的具體的生成器實例相關。

          終于理解了它是如何把復雜對象的構建和它的表示分離的,也理解了為什么同樣的構建過程。

          2、 動機

          3、 適用性

          當創建復雜對象的算法應該獨立于該對象的組成以及他們的裝配方式時——對象的組成及裝配方式是生成器的抽象接口來表示的。而對象的創建的算法是有生成器的子類實現的。

          當構造過程必須允許被構造的對象有不同的表示時——也就是,同樣的構造過程,可以生成不同的產品。這里是通過配置導向器的生成器的子類實例來實現的。

          4、 結構

          wps_clip_image-372

          5、 參與者

          l Builder:為創建一個product對象的各個部件指定抽象接口。這些部件最終構成了product,而對這些抽象接口的調用,則是裝配product的操作,調用的次數,入參等不同,則最終生成的product也不同。product本身的構造過程可能會非常復雜。但是,這些復雜度對Director(導向者)是隱藏的。這些抽象的接口描述了產品的組成以及裝配方式。

          l ConcreteBuilder:實現Builder的接口以構造和裝配產品的各個部件;定義并明確它所創建的表示;提供一個檢索產品的接口。這個類實現了Builder的抽象接口,從而可以創建不同的表示,但是組成和裝配過程還是一樣的。

          l Director:構造一個使用Builder抽象接口的對象。更對象根據Builder的接口來裝配并生產產品。

          l Product:表示被構造的復雜的對象。ConcreteBuilder創建該產品的內部表示,并定義它的裝配過程;包含定義組成部件的類,包括將這些部件裝配成最終產品的接口。

          6、 協作

          l 客戶創建Director對象,并用它想要的Builder對象進行配置。

          l 一旦產品不僅被生成,導向器就會通知生成器。

          l 生成器處理導向器請求,并且將部件添加到該產品中。

          l 客戶從生成器中檢索產品。

          wps_clip_image-911

          7、 效果

          l 它使你可以改變一個產品的內部表示。Builder提供給Director的抽象接口可以使生成器隱藏這個產品的表示和內部結構。它同時也隱藏了該產品時如何裝配的。因為產品時通過抽象接口構造的,你改變該產品的內部表示時,所要做的只是定義一個新的生成器。

          l 它將構造代碼和表示代碼分開。Builder模式通過封裝一個復雜對象的創建和表示方式提高了對象的模塊性。每個ConcreteBuilder包含了創建和裝配一個特定產品的所有代碼。

          l 它可以使你對構造過程進行更精細的控制。

          8、 實現

          l 通常一個抽象的Builder類為導向者可能要求創建的每一個構件定義一個操作。這些操作缺省什么也不做。一個ConcreteBuilder類對它有興趣創建的構建重定義這些操作。

          l 裝配和構造接口:一般構造請求的結果只是被添加到產品中,特殊情況下,需要返回給導向器。

          l 產品沒有抽象類:一般他們沒有公共的部分。如果有也可以設置一個抽象類。

          l Builder中缺省的方法為空。

          9、 代碼示例

          class MazeBuilder {

          public:

              virtual void BuildMaze() { }//部件的構造方法

              virtual void BuildRoom(int room) { }

              virtual void BuildDoor(int roomFrom, int roomTo) { }

              virtual Maze* GetMaze() { return 0; }

          protected:

              MazeBuilder();

          };//MazeBuilder是生成器

          class StandardMazeBuilder : public MazeBuilder {

          public:

              StandardMazeBuilder();

          /*

          */

              virtual void BuildMaze();

              virtual void BuildRoom(int);

              virtual void BuildDoor(int, int);

          /*

          */

              virtual Maze* GetMaze();

          private:

              Direction CommonWall(Room*, Room*);

              Maze* _currentMaze;

          };//StandardMazeBuilder是ConcreteBuilder,提供部件的具體構造代碼

          Maze* MazeGame::CreateMaze (MazeBuilder& builder) {

              builder.BuildMaze();

              builder.BuildRoom(1);

              builder.BuildRoom(2);

              builder.BuildDoor(1, 2);

              return builder.GetMaze();

          }//CreateMaze是導向器,調用生成器的抽象接口完成產品的構造過程。

          //下面代碼描述產品的構造過程

          Maze* maze;//最終的產品

          MazeGame game;//Director,導航者

          StandardMazeBuilder builder;//ConcreteBuilder,實際的構造類

          game.CreateMaze(builder);//開始裝配

          maze = builder.GetMaze();//獲取裝配后的產品

          posted @ 2010-03-31 21:07 常高偉 閱讀(240) | 評論 (0)編輯 收藏

          1.1 ABSTRACT FACTORY 抽象工廠

          1、 意圖

          提供一個創建一系列相關或相互依賴對象的接口,而無需指定他們具體的類。

          2、 動機

          “客戶僅與抽象定義的接口交互,而不使用特定的具體類的接口。”

          這里的主要的思想是封裝對象的創建的過程。客戶端可以不需要知道具體要創建那些對象,而只需要知道創建某一系列的對象所用到的“工廠對象”即可。

          3、 適用性

          一個系統要獨立于它的產品的創建、組合和表示時。 

          一個系統要由多個產品系列中的一個來配置時。 

          當你要強調一系列相關的產品對象的設計以便進行聯合使用時。 

          當你提供一個產品類庫,而只想顯示它們的接口而不是實現時。 

          4、 結構

          wps_clip_image-278

          5、 參與者

          AbstractFactory:創建一系列對象的抽象類。

          ConcreteFactory:實現具體創建產品對象的操作。

          AbstractProduct:為一類產品對象聲明一個接口。

          ConcreteProduct:定義一個被相應的具體工廠創建的對象;實現AbstractProduct接口。

          Client:僅使用AbstractFactory和AbstractProduct類聲明的接口。

          6、 協作

          在運行時刻,創建一個ConcreteFactory實例,它創建具有特定實現的對象。為創建不同的對象,客戶應使用不同的具體工廠。

          AbstractFactory將具體對象的創建延遲到它的子類ConcreteFactory中。

          7、 效果

          1) 它分離了具體的類:一個工廠封裝創建產品的責任和過程,它將客戶和類的實現分離。客戶通過抽象接口操作實例。產品的類名也在具體工廠實現中分離,他們不出現在客戶代碼中。

          2) 它使得易于交互產品系列。

          3) 它有利于產品的一致性。

          4) 難于支持新的種類。

          posted @ 2010-03-03 20:27 常高偉 閱讀(190) | 評論 (0)編輯 收藏

          1.1 設計模式怎樣解決設計問題

          1.1.1 尋找合適的對象

          面向對象設計最困難的部分是將系統分解為對象的集合。

          設計的許多對象來源于現實世界的分析模型,這里和領域驅動設計有點關聯。分析所得到的類,很多事現實中并不存在的類。這是抽象的結果。設計中的抽象對于產生靈活的設計至關重要。就像我設計的一個流程調度模型。

          1.1.2 決定對象的粒度

          記筆記可以讓我達到沉流的狀態。

          1.1.3 指定對象接口
          1.1.4 描述對象實現

          OMT表示法:

          1、 對象:最上面的黑體表示類名,下面依次是操作,數據。

          wps_clip_image-219

          2、 實例化:虛線箭頭表示一個類實例化另外一個對象。

          wps_clip_image-245

          3、 繼承:豎線和三角表示繼承關系。

          wps_clip_image-263

          4、 抽象類:類名以黑體斜體表示,操作也用斜體表示。

          5、 引用

          wps_clip_image-293

          箭頭加黑點表示一個類引用另外一個類。

          重點:

          1、 類的繼承和接口繼承的比較

          對象的類和對象的類型的區別:

          對象的類定義了對象是怎樣實現的,同時也定義了對象內部狀態和操作的實現。對象的類型只與它的接口有關。一個對象可以由多個類型(支持多個接口),不同類的對象可以有相同的類型。

          類和類型緊密相連,類定義了對象的操作,也定義了對象的類型。

          類的繼承和接口的繼承的差別:

          c++中接口繼承接近于公有繼承純抽象類。純實現繼承或純類繼承接近于私有繼承。

          2、 對接口編程,而不是對實現編程——面向對象設計的第一個原則

          1.1.5 運用復用機制

          1、 繼承和組合的比較

          繼承是一種白箱復用,父類的內部細節對子類可見。

          對象組合彼此不知道對方內部細節,成為黑箱復用。

          繼承的優缺點:

          1) 子類可以直接重定義父類的操作。

          2) 編譯時刻決定了,無法在運行期間更改。

          3) 子類要知道父類的實現細節,這樣就部分破壞了封裝性。子類和父類依賴過于緊密,父類的某些變化必然導致子類的變化。開發過程中遇到過類似的問題。這種依賴,限制了靈活性以及復用性。比如,服務體系中經常出現這樣的問題,導致代碼拷貝。

          組合(通過獲得對象的引用而在運行時刻動態的定義)的優缺點:

          1) 對象間通過接口彼此交互。

          2) 對象只能通過接口訪問,不要也不能知道對方細節,這樣不會破壞封裝性。

          3) 運行時刻可以使用另外一個對象替換這個對象,提高了靈活性。

          4) 對象的實現基于接口編寫,所以實現上存在較少的依賴關系。

          5) 優先使用組合有助于保持每個類被封裝,并被集中在單個任務上,提高整體內聚性。類和類的層次都維持一個較小的規模,

          6) 基于對象組合的設計會有更多的對象(而又較少的類),且系統的行為依賴于對象間的關系而不是定義在某個類的內部。

          理想的情況下,應該通過組合原有構件實現新的功能,而不是創建新的構件。

          面向對象設計的第二個原則:優先使用對象組合,而不是類繼承。

          2、 委托

          委托時一種組合方法,它是組合具有與繼承同樣的能力。

          委托的主要優點在于它便于在運行時刻組合對象操作,以及更改操作的組合方式。它是軟件更加的靈活。

          和其他的技術方案相同,它也存在不足之處:增加了軟件的復雜度——動態的,高度參數化的軟件比靜態的軟件更難于理解。

          3、 繼承和參數化類型的比較

          1.1.6 關聯運行時刻的結構和編譯時刻的結構
          1.1.7 設計應支持變化

          設計應該支持變化——所說的是,一個設計方案,對變化要有一定的適應性,即封裝變化。

          變化是導致重新設計的原因。設計要對一定范圍內的變化友好。

          4、 

          對于程序的分層設計,對于處于同一分層的模塊,對外應保持一定的抽象,并且,使用同種類型的通信協議。

          posted @ 2010-03-03 20:26 常高偉 閱讀(435) | 評論 (0)編輯 收藏

          1.1 變量存儲域

          1.1.1 一個示例

          pang123hui首先提供了一個網上流傳的學習代碼示例:

          int a = 0; //全局區 

          void main() 

          {

          int b; //棧 

          char s[] = “abc”; //s在棧,abc在文字常量區 

          char *p1,*p2; //棧 

          char *p3 = "123456"; //123456在常量區,p3在棧上 

          static int c =0; //全局區 

          p1 = (char *)malloc(10); //p1在棧,分配的10字節在堆 

          p2 = (char *)malloc(20); //p2在棧,分配的20字節在堆 

          strcpy(p1, "123456"); //123456放在常量區 

          }

          這個代碼示例中出現了“全局區”,“棧”,“文字常量區”,“堆”等詞語。為了統一,我們使用《C專家編程》中的說法:堆棧段,BSS段,數據段,文本段。

          各個段的作用如下:

          1、 文本段:包含程序的指令,它在程序的執行過程中一般不會改變。

          2、 數據段:包含了經過初始化的全局變量和靜態變量,以及他們的值。

          3、 BSS段:包含未經初始化的全局變量和靜態變量。

          4、 堆棧段:包含了函數內部聲明的局部變量。

          當然,上面段的作用不僅于此,具體的作用會在下面的知識點中介紹。

          1.1.2 通過代碼測試變量的存儲位置

          Linux下可以通過系統命令“size”查看可以執行程序各個段的大小。但是,可執行程序中的段結構和運行中程序在內存中的段結構并不完全相同,但是有一定的映射關系。具體如下圖所示(圖片信息來自《C專家編程》):

          wps_clip_image-696

          下面通過代碼示例和“size”來研究變量的存儲區域。

          test.c

          int main()

          {

          return 1;

          }

          編譯,并且查看可執行程序各個段的大小:

          wps_clip_image-779

          更改test.c:

          int g_data;

          int main()

          {

          return 1;

          }

          編譯,并且查看可執行程序各個段的大小:

          wps_clip_image-849

          可以發現,文本段,數據段都沒有發送變化,而BSS段增加了4個字節。

          結論1:未初始化的全局變量保存在BSS段中

          繼續:

          int g_data = 1;

          int main()

          {

          return 1;

          }

          編譯:

          wps_clip_image-958

          可以發現,BSS段和文本段相同,而數據段增加了4個字節。

          結論2:經過初始化的全局變量保存在數據段中

          繼續:

          int main()

          {

          static int g_data;

          return 1;

          }

          編譯:

          wps_clip_image-1066

          可以發現,文本段,數據段都沒有發送變化,而BSS段增加了4個字節。

          結論3:未初始化的靜態變量保存在BSS段中

          繼續:

          int main()

          {

          static int g_data = 1;

          return 1;

          }

          編譯:

          wps_clip_image-1183

          可以發現,BSS段和文本段相同,而數據段增加了4個字節。

          結論4:經過初始化的靜態變量保存在數據段中

          繼續:

          int main()

          {

          int i_data = 1;

          return 1;

          }

          編譯:

          wps_clip_image-1288

          可以發現,BSS段和和數據段相同,而文本段增加了16個字節。局部變量會在執行的時候在堆棧段中生成,函數執行完畢后釋放。

          結論5:函數內部聲明的局部變量保存在堆棧段中

          繼續:

          const int g_data = 1;

          int main()

          {

          return 1;

          }

          編譯:

          wps_clip_image-1430

          把全局變量定義為“const”后,也許你會感到奇怪,怎么BSS段和數據段都沒有發生變化,而文本段卻增加了4個字節。

          結論6:const修飾的全局變量保存在文本段中

          那么,const的局部變量?

          繼續:

          int main()

          {

          const int i_data = 1;

          return 1;

          }

          編譯:

          wps_clip_image-1587

          結論7:const修飾的局部變量保存在堆棧段中

          繼續:

          char *pstr = "";

          int main()

          {

          return 1;

          }

          編譯:

          wps_clip_image-1666

          在做一下更改:

          char *pstr = "123456789";

          int main()

          {

          return 1;

          }

          編譯:

          wps_clip_image-1733

          可以發現,前后數據段和BSS段大小均未發生變化,而文本段增加了9個字節。

          結論8:字符串常量保存在文本段中

          1.1.3 結論

          1、 經過初始化的全局變量和靜態變量保存在數據段中。

          2、 未經初始化的全局變量和靜態變量保存在BSS段。

          3、 函數內部聲明的局部變量保存在堆棧段中。

          4、 const修飾的全局變量保存在文本段中,const修飾的局部變量保存在堆棧段中。

          5、 字符串常量保存在文本段中。

          1.1.4 擴展閱讀

          《C專家編程》第6章——詳細介紹各個段的作用。

          posted @ 2010-03-03 02:38 常高偉 閱讀(295) | 評論 (0)編輯 收藏

          讀S計劃的理念

          自助、互助,共同進步!

           

          讀S計劃的初衷

          現在有很多學習方式,搜索引擎、論壇、博客,qq群等等,那么我這樣的計劃還有存在的必要么?這個計劃的獨特之處在哪里?

          讀S計劃的獨特之處不在于其學習內容和方式,學習的內容我們可以根據實際情況調整,可以由多個人同時引導多個學習方向,這些都不是程式化的,也不是重點。

          讀S計劃的獨特之處在于其理念:致力于形成一種有計劃、有組織,強調互動、互助和共同進步的學習模式和氛圍。讀S計劃不是一個單純的興趣小組,我們會按照計劃不斷的切換話題。讀S計劃也不是提問的好地方,在每個階段,我們都有限定的討論范圍,所以無關的問題很容易被忽略。讀S計劃希望多數人以積極的心態參與進來,更多的討論、研究和貢獻您的想法,而不是被動的接受和看,我們不希望將學習計劃做成課堂模式,我們希望做成項目的模式 。讀S計劃致力于將信息以信息本身進行聚合,而不是以人進行聚合,因為學習計劃在不斷推進,所以不存在永遠的權威人士,但知識本身是客觀、中立的、權威的。

           

          共建新型技術社區

          建立新型的、關系緊密的、以知識體系為軸、強調經驗積累與分享的的技術社區。

           

          更深入了解此計劃,可以閱讀:

          《技術族譜 之 讀S計劃:讓我們一起快樂學習》

          《準備啟動一個開源項目 - 技術族譜 - 先期利用Goolge云計算平臺》

          《關于“讀S計劃”的聊天記錄:統一理念,確定目前的工作》

          《授人以“魚”,不如授人以“漁”,放棄一個目標,設定另一個目標》

          期待你加入我們的群組:http://hi.csdn.net/space-mtag-tagid-37.html

          posted @ 2009-10-14 21:43 常高偉 閱讀(206) | 評論 (0)編輯 收藏

          上面一篇文章大致描述了一下插件開發框架整體結構。這篇描述一下核心層的設計和實現。

          至于核心層的設計,我想借鑒 一下微內核的思想。核心層只負責實現下面幾個功能:

          1、 插件的加載,檢測,初始化。

          2、 服務的注冊。

          3、 服務的調用。

          4、 服務的管理。

          插件的加載,檢測,初始化

          插件的加載利用linux共享庫的動態加載技術。具體的方法可以看一下IBM網站的一篇資料《Linux 動態庫剖析》

          服務的注冊

          服務的注冊與調用采用表驅動的方法。核心層中維護一個服務注冊表。

          //插件間交互消息類型
          typedef enum __Service_Type
          {
              Service_Max,
          }Service_Type;
          //插件用于和其他插件通信接口函數,由插件提供。
          typedef PRsp_Ele_Stream (*PF_Invoke_Service_Func)(PReq_Ele_Stream pele_str);
          //驅動表
          typedef PF_Invoke_Service_Func Service_Drive_Table[Service_Max];

          驅動表是一個數組,下標為插件間交互消息類型,成員為插件提供的接收的消息處理函數,由插件初始化的時候,調用插件框架的的注冊函數注冊到驅動表。

          插件的初始化實現為:
          //插件用于注冊處理的消息類型的函數,由插件框架提供。
          typedef RET_RESULT (*PF_Service_Register_Func)(Service_Type service_type);
          //插件用于和其他插件通信接口函數,由插件框架提供。
          typedef PRsp_Ele_Stream (*PF_Invoke_Service_Func)(PReq_Ele_Stream pele_str);
          //插件回復響應函數。插件收到異步請求后,處理完成后,發送響應消息給請求的插件。由插件框架提供
          typedef void (*PF_Send_Response_Func)(PRsp_Ele_Stream pele_str);
          //初始化插件信息
          typedef struct Plugin_Init_St
          {
              PF_Service_Register_Func register_func;//服務注冊函數,要注冊一系列的枚舉值。插件可以處理的服務枚舉值
              PF_Invoke_Service_Func invoke_serv_func;//和其他組件交互時,調用的用于和其他組件交互的函數。發送請求消息。
              PF_Send_Response_Func send_rsp_func;//再設計一個回復響應消息的接口。收到異步請求后,處理完畢后通知請求模塊處理結果。
          } Plugin_Init_St, *PPlugin_Init_St;
          //初始化插件函數,類似于構造函數。由插件提供,供插件框架加載插件時初始化插件使用。
          void PF_Init_Plugin(PPlugin_Init_St pinit_info);

                插件在函數PF_Init_Plugin中調用函數register_func來注冊插件要處理的消息類型。

          服務的調用
          //信元結構體
          typedef struct Ele_St
          {
              Ele_Tag tag;
              Ele_Length len;
              Ele_Value  value;
              PEle_St next;
          }Ele_St, *PEle_St;
          //請求消息,信元流格式。
          typedef struct Req_Ele_Stream
          {
              Plugin_ID src_id;//源插件id
              Service_Type req_type;//請求類型
              PEle_St ele;
          } Req_Ele_Stream, *PReq_Ele_Stream;
          //響應消息,信元流格式。
          typedef struct Rsp_Ele_Stream
          {
              Plugin_ID dest_id;//目的插件id
              Service_Type req_type;//響應對應的請求的類型。
              Execute_Result result;//記錄執行結果
              Execute_Reason reason;//記錄執行結果的原因
              PEle_St ele;
          } Rsp_Ele_Stream, *PRsp_Ele_Stream;
          //接收插件調用服務請求函數,由插件提供,入參為請求信元流。返回值為響應信元流,用于同步請求處理。
          PRsp_Ele_Stream PF_Receive_Invoke_Proc(PReq_Ele_Stream pele_str);
          //插件收到響應消息的處理入口函數,由插件提供。如此為響應信元流。
          void PF_Receive_Rsponse_Porc(PRsp_Ele_Stream pele_str);

          插件間的依賴關系是通過信元流來實現的。至于信元流的使用在我的另一篇博客《使用信元流(TLVStream)規范、簡化模塊(C/C++)間交互 》 中有描述。插件對外的接口都是統一的。

          如果插件要和其他的插件通信,則調用PF_Init_Plugin函數的傳遞的服務調用接口: invoke_serv_func。插件框架根據信元流的類型,查找驅動表,找到對應的服務接收函數。插件用函數 PF_Receive_Invoke_Proc接受其他插件的請求,此函數是插件想插件框架主動注冊到驅動表的。

          如果服務時同步的,這直接通過此函數返回,返回的信息在響應信元流中。如果是異步的請求,這插件在處理完成后,通過 send_rsp_func函數來發送響應。

          插件的卸載
          //卸載插件時調用的函數,類似于析構函數。由插件提供,供插件框架卸載插件時調用。
          void PF_Destroy_Func();

          posted @ 2009-10-12 20:20 常高偉 閱讀(1231) | 評論 (0)編輯 收藏

          這幾天為了設計插件開發框架,嘗試用了一下發散思維來思考問題。中間看過依賴注入,AOP(面向方面編程),以及契約式設計等。雖然有些工具無法直接使用,但是這些思想還是可以借鑒的,比如依賴注入,契約式設計。至于AOP,和工具相關性較大,雖然思想不錯,但是無法直接在C++中使用。

          我設計的插件間的依賴不是通過接口實現的,而是通過插件間的數據(信元流)。而信元流的檢測可以使用契約來檢查。

          插件開發框架的總體結構

          結構圖

          微內核

          1、 負責插件的加載,檢測,初始化。

          2、 負責服務的注冊。

          3、 負責服務的調用。

          4、 服務的管理。

          擴展層:

          1、 日志的打印。

          2、 消息(信元流)的解釋,將二進制格式解釋為文本。便于定位。

          3、 消息和日志的追蹤。

          分布式處理層:

          1、 用于和其他的框架通信。

          2、 和其他的框架搭配,形成一個分布式的系統。

          自動化測試框架層:

          1、 集成 cppunit 。

          2、 自動化集成測試框架。

          3、 自動化功能測試框架。

          和第三方框架集成層:

          1 、和 第三方框架 集成層。

          posted @ 2009-10-12 20:19 常高偉 閱讀(756) | 評論 (0)編輯 收藏

          原創  使用信元流(TLVStream)規范、簡化模塊(C/C++)間交互 收藏

            問題描述:
            在軟件開發過程中,一般會對復雜的現實世界進行抽象,并且采用分而治之的策略,將大系統分解為子系統,將子系統分解為一個個的模塊。模塊間的通信一般采用函數調用的方式,這樣會產生一些問題:
             1. 模塊間的接口增多會導致模塊間緊密耦合,不利于模塊的重用、調試、維護。
             2. 接口函數參數會很復雜,容易出現很龐大,功能面面俱到的結構體。
             3. 不利于擴展,新增功能要新增接口函數,或者修改接口參數。


          信元流的定義
            信元流是一種處理數據的方式。它對現實世界的數據進行抽象,形成一個一個的信元,然后由這些信元以一定的方式組合起來,形成信元流,來傳遞數據。下面信元流的實現。
            信元的實現
            信元由三部分組成,分別是信元標識(Tag),信元長度(Length),信元值(Value),即TLV格式。下面是一個信元的c定義:   
            typedef struct __ELEMENT_ST

               {

                    ELE_TAG tag;

                    ELE_LEN len;

                    ELE_VALUE value;

               }ELEMENT_ST;

            信元流的實現:
            信元流分為兩部分,信元流頭部(head)和體部(body)。head部分用于記錄信元流的整體描述,比如,信元流的起始模塊,目的模塊,以及信元流的消息類型等等。當然,也可以根據需要自己進行擴展。body部分包括信元流中信元的總大小,即ELE_MSG_BODY中body的長度。

                  typedef struct __ELEMENT_STREAM_ST
                  {
                      MOD_ID src_mod;
                      MOD_ID des_mod;
                      ELE_MSG_TYPE type;
                      ELE_MSG_BODY body;
                  }ELEMENT_STREAM_ST;
                  typedef struct __ELE_MSG_BODY
                  {
                      ELE_MSG_LEN len;
                      char body[MAX_ELE_MSG_BODY_LEN];
                  } ELE_MSG_BODY;

            構造信元流
            定義好結構體后,下面定義兩個函數,分別向信元體中添加信元和刪除信元。
             //pbody信元體的指針。這里沒有使用信元流的指針是為了讓函數的重用性更好,用戶可以自己定義信元流。
                  //tag:添加信元的TAG值,len:添加信元的長度。pvale:添加信元的值。
                  int AddElementToStreamBody(ELE_MSG_BODY *pbody, ELE_TAG tag, ELE_LEN len, void *pvalue);
                  //pbody信元體的指針。 //tag:獲取信元的TAG值,buf_len:pbuf的長度。pbuf:目標緩沖區,要把信元VALUE的值寫入吃緩沖區。
                  int GetElementFromStreamBody(ELE_MSG_BODY *pbody, ELE_TAG tag, int buf_len, void *pbuf);
            信元流的body是一個緩沖區,信元在里面順序排列。信元在body中順序不影響它的獲取。添加和獲取的方法比較簡單,不再贅述。


            一個信元流的實例
            下面來舉一個具體的例子來介紹一下信元流的使用,以及它的優點。
            假如由兩個模塊A和B。A模塊負責處理業務邏輯,B模塊負責處理呼叫控制。A調用B的一個接口發起呼叫,接口如下。
                  typedef struct __MAKE_CALL_ST
                  {
                      char caller_num[MAX_NUM_LEN];//主叫號碼
                      char called_num[MAX_NUM_LEN];//被叫號碼
                  }MAKE_CALL_ST;
                  int MakeCall(MAKE_CALL_ST *pcall_info);

            后面需求有更改,某些情況下藥攜帶主叫的callid信息。結構體會變成:
              typedef struct __MAKE_CALL_ST
              {
                  char caller_num[MAX_NUM_LEN];//主叫號碼
                  char called_num[MAX_NUM_LEN];//被叫號碼
                  CALL_ID caller_callid;
              }MAKE_CALL_ST;
            某些情況下又需要攜帶主叫的SDP信息,結構體會變成:
              typedef struct __MAKE_CALL_ST
              {
                  char caller_num[MAX_NUM_LEN];//主叫號碼
                  char called_num[MAX_NUM_LEN];//被叫號碼
                  CALL_ID caller_callid;
                  SDP_INFO call_sdp;
              }MAKE_CALL_ST;
            隨著需求的增加,這個結構體會越來越大,并且,其中的成員在某些情況下要使用,某些情況下又不使用,造成模塊間的冗余數據。
            當然,你也可以采用其他的方法,比如,再多定義幾個接口和結構體。但是這樣的話接口和結構體的數量會呈爆炸式的增長。
            使用信元流可以很好的解決這個問題。把號碼,callid,sdp等全部定義成信元,需要的時候塞進去,不需要的話就不添加。另外還有一個好處就是,一個模塊可以對外只公布一個接口,來收取信元流,然后在根據信元流的類型進行分別處理。這樣,一個模塊對外就只有一個接口了,模塊間的耦合性會降低。


          一點改進
            上面定義的信元流的格式在實際使用的過程中還是碰到了一些問題,最突出的就是,信元流的大小是固定死的。這種情況下,如果信元信息很小,會導致空間浪費,效率降低;如果信元信息很多,信元流的空間又不夠。

            可以對上面的這種方案進行一下優化,把信元的定義更改為:

            typedef struct __ELEMENT_ST

               {

                    ELE_TAG tag;

                    ELE_LEN len;

                    ELE_VALUE value;

                    ELEMENT_ST  *pnext_ele;//下一個信元流

               }ELEMENT_ST;

            將信元流的定義更改為:

                  typedef struct __ELEMENT_STREAM_ST
                  {
                      MOD_ID src_mod;
                      MOD_ID des_mod;
                      ELE_MSG_TYPE type;
                      ELEMENT_ST  *pfirst_ele;//第一個信元流
                  }ELEMENT_STREAM_ST;

            將信元流和信元更改為動態申請的內存。這樣既可以提高效率,有沒有了大小的限制。

            需要增加兩個接口,來申請和釋放信元流。

            唯一不好的地方時,動態申請的內存需要程序員記得釋放,否則會內存泄露。不過還有一個方法,即增加一個申請信元流的函數,如下
                  ELEMENT_STREAM_ST *GetEleStream()
                  {
                      static     ELEMENT_STREAM_ST *pstream = NULL;
                      if (NULL != pstream)
                      {
                          FreeEleStream(pstream);   
                          pstream = NULL;
                      }
                      pstream = AllocteEleStream();
                      return pstream;
                  }

            這樣的話,通過函數GetEleStream獲取的信元流,只在函數范圍內有效,退出函數后,立即無效。

          posted @ 2009-10-12 20:18 常高偉 閱讀(362) | 評論 (0)編輯 收藏

          在這一系列的上一個文章中,介紹了構建C/C++插件開發框架的初步設想,下面我會一步步的向下展開,來實現我的這個設想。

          今天主要談一下我對這個框架的功能認識,或是期望。昨天看了一篇關于持續集成能力成熟度模型 的一篇文章,受此啟發,我對此框架的認識漸漸清晰。

          這個框架可以當做我們公司底層產品(交換機,資源服務器等)的基礎設施。上層基于java開發的產品可以直接在OSGI上開發。

          核心功能:

          1、最重要的一個功能是,提供一個模塊化的編程模型,促進模塊化軟件開發,真正的實現針對接口編程。

          2、提供一個有助于提高模塊可重用性的基礎設施。

          3、提供一個C/C++插件的運行環境。

          4、提供一個動態插件框架,插件可以動態更改,而無需重啟系統。這個功能雖然不難實現,但是用處好像不是很大。


          擴展部分功能:

          1、支持分布式系統結構,多個運行框架組合起來形成一個系統,對模塊內部隱藏遠程通訊細節。

          2、支持系統的分層架構。

          3、能夠和其他的開發框架進行集成,比如OSGI,SCA等。

          4、多個運行框架中,能夠實現對運行框架的有效管理。

          5、概念上要實現類似于SCA中component(構件),composite(組合構件),Domain(域)的概念。


          開發部分功能:

          1、為了簡化開發,開發一個Eclipse插件,用于開發框架中的C/C++插件。能夠根據插件開發向導,最終生成符合插件規范的公共代碼,配置文件,Makefile文件等。


          調試部分功能:

          1、提供一個統一的日志處理函數,可以集成Log4cpp。

          2、提供模塊間的消息日志,以及框架對外的接口日志。

          3、提供消息和日志的追蹤功能,能將和某事件相關的消息和日志單獨提取出來。

          4、提供資源監測功能,監測對資源(內存,套接字,文件句柄等)的使用情況。


          測試部分功能:

          1、集成一些單元測試框架,比如unitcpp,達到自動化單元測試的目標。

          2、自己實現自動化集成測試框架,并且開發相應的Eclipse插件,簡化集成測試(利用腳本和信元流)。

          3、集成原有的自動化功能測試框架flowtest,并且開發相應的Eclipse插件,簡化功能測試。

          4、實現性能測試,監測框架。


          部署部分功能:

          1、實現自動化部署。特別是在分布式應用的情況下。

          2、提供一個命令行程序,通過命令更改系統配置,管理插件。

          posted @ 2009-10-12 20:18 常高偉 閱讀(860) | 評論 (0)編輯 收藏

           最近一直在學習OSGI方面的知識。買了一本《OSGI原理和最佳實踐》,可是還沒有到。遺憾的是,OSGI目前的幾個開源框架只支持Java,對C和C++都不支持的。可惜我們公司目前主要的開發語言還是c和c++,即便是引進OSGI,所得的好處范圍有限。而我對松散耦合的模塊化開發向往已久。查了一下OSGI對C++支持的好像是有一個開源項目,不過好像應用范圍很小。而SCA標準中是有對C++實現模型的支持的,但是幾個開源的框架目前還只支持JAVA。

            昨天看了丁亮的轉載的一篇博客《C/C++:構建你自己的插件框架 》,原文的鏈接:http://blog.chinaunix.net/u/12783/showart_662937.html 。看了一下里面講的方法,自己倒是可以實現。所以有了構建自己的c/c++插件開發框架的想法。今天先寫一下初步的設想。

          C/C++插件開發框架的要素

            BlueDavy有一篇介紹服務框架要素的文章(鏈接:http://www.aygfsteel.com/BlueDavy/archive/2009/08/28/172259.html )。我的插件框架也要考慮、解決以下的幾個問題:

            1、如何注冊插件;

            2、如何調用插件;

            3、如何測試插件;

            4、插件的生命周期管理;

            5、插件的管理和維護;

            6、插件的組裝;

            7、插件的出錯處理;

            8、服務事件的廣播和訂閱(這個目前還沒有考慮要支持);

            其中有幾個點很重要:1)插件框架要能夠使模塊松散耦合,做到真正的面向接口編程;2)框架要支持自動化測試:包括單元測試,集成測試;3)簡化部署;4)支持分布式,模塊可以調用框架外的插件。

          采用的技術
            插件框架要解決的一個問題就是插件的動態加載能力。這里可以使用共享庫的動態加載技術。當然,為了簡單,第一步只考慮做一個linux下的插件框架。

            總體結構

            框架的總體結構上,參考OSGI的“微內核+系統插件+應用插件”結構。這里要好好考慮一下把什么做在內核中。關于微內核結構,以前我做個一個微內核流程引擎,會在后面有時間和大家分享。

            框架中模塊間的數據傳送,有兩種解決方法:一是普元采用的XML數據總線的做法。優點是擴展性好,可讀性好。但是速度有些慢。二是采用我熟悉的信元流。優點的效率高,訪問方便,但是可讀性差一點,另外跨框架的數據傳送,需要考慮網絡字節序的問題。

            對于框架間的通信,通過系統插件封裝,對應用插件隱藏通信細節。

                部署

                努力做到一鍵式部署。

          posted @ 2009-10-12 20:16 常高偉 閱讀(2428) | 評論 (0)編輯 收藏

          《重構》第三章學習筆記

          我們必須培養自己的判斷力,來決定在什么時候進行重構。

          1.1  Duplicate Code(重復代碼)

          如果你在一個以上地點看到相同的程序結構,那么將他們合而為一會更好。

          1.2  Long Method(過長函數)

          擁有短函數的對象會活得比較好,比較長。

          間接層所能帶來的全部益處:解釋能力(可讀性),共享能力(重用性),選擇能力(?)。

          現在OO 語言基本解決了函數調用所產生的開銷。

          “ 你應該更積極進去的分解函數。我們遵循這樣一條原則:每當感覺需要以注釋來說明點什么的時候,我們就把需要說明的東西寫進一個函數中,并以其用途(而非實現手法)命名。我們可以對一組甚至短短一行代碼(擁有復雜邏輯,難以理解)做這件事。哪怕替換后的函數調用動作比函數自身還長,只要函數名稱能夠解釋其用途,我們也該毫不猶豫的這么做。關鍵不在于函數的長度,而在于“做什么”和“如何做”之間的語義距離。 ”

          “如何確定該提煉哪一段代碼?一個很好的技巧是:尋找注釋。它們通常是指出“代碼用途和實現手法間的語義距離”的信號。如果代碼需要用注釋來說明其用途,那么就要考慮把這段代碼提煉成獨立的函數,并且用注釋來為此函數命名。”

          復雜條件式和循環液常常是提煉的信號。

          1.3  Large Class(過大類)

          如果想利用單一的class 做太多的事情,其內往往會出現太多的 instance 變量。

          如果class 中擁有太多的代碼,也是“代碼重復、混亂、死亡”的絕佳滋生點。

          1.4  Long Parameter List(過長的參數列表)

          過長的產生導致程序難以理解。

          1.5  Divergent Change(發散式變化)

          “ 一個class 受多個外界變化的影響 ”,則把這多個變化封裝成一個新的類。即“ 將總是一起變化的東西放在一起 ”

          針對外界某一變化所有相應的修改,都應該只發生在單一的class 中,而這個 class 的所有內容都應該反映該外界變化。總的思想就是,封裝變化。這個地方和設計模式的想法是一致的。

          1.6  Shotgun Surgery(散彈式修改)

          和發散式變化不同,每次遇到變化,都要在多個class 中進行小的修改以響應之。他們分散在多處,很容易出錯。

          這里的主要思想是集中變化。

          散彈式修改指的是,“ 一種變化引發多個class 的修改 ”,發散式變化指的是“ 一個class 受多個外界變化的影響 ”。

          這兩種情況下,通過重構, 使“外界變化”和“待修改類”呈一對一關系 的理想境地。

          1.7  Feature Envy(依戀情節)

          某個函數對其他類的數據的興趣,高過對host class 的興趣。即對其他的類的數據的依賴十分大。

          1.8  Data Clumps(數據泥團)

          數據泥團指的是總是綁定在一起出現的數據。

          一個好的評斷方法:刪除眾多數據中的一項數據,其他數據是否是因而失去了意義?如果他們不再有意義:你應該為他們產生一個新的對象。

          形成新的對象后,可以根據Feature Envy 將一些操作移至此對象中。

          1.9  Primitive Obsession(基本型別偏執)

          建立多個很小,但是很靈活的對象。

          1.10  Switch Statements( switch 驚悚現身)

          使用面向對象編程,要少用switch 和 case 語句。而是用多態來替換它。

          1.11  Parallel Inheritance Hierarchies(平行繼承體系)

          每當你為一個class 增加一個 subclass 的時候,必須為另一個 class 增加一個 subclass 。一般這兩個 class 的前綴相同。

          1.12  Lazy Class(冗贅類)

          類顯得多余,沒有價值。

          1.13  Speculative Generality(夸夸其談未來性)

          這個往往是過度設計的結果:對某種變化的應對,而這種變化沒有發生。

          1.14  Temporary Field(令人迷惑的暫時值域)

          變量只在特定的情形下有效,而并不是所有的情況下有效。很多情況下,這些值域應該不屬于此class ,而應該單獨的提取成新的類。

          1.15  Message Chains(過度耦合的消息鏈)

          用戶向一個對象索取另一個對象,然后在向后者索求另一個對象,然后在索求另一個對象——客戶與查找過程的航行結構緊密耦合。

          1.16  Middle Man(中間轉手人)

          對象的基本特征之一就是封裝——對外部世界隱藏實現細節——封裝往往伴隨委托。委托的過度運行,就導致了Middle Man 。

          1.17  Inappropriate Intimacy (親密關系)

          兩個class 之間的關系過于親密。比如,花大量的時間探究彼此的 private 成分。

          1.18  Alternative Classes with Different Interface(異曲同工的類)

          類名不同,但是功能相似。

          1.19  Incomplete Library Class(不完美的程序類庫)

          基礎類庫無法滿足實際的需求。

          1.20  Data Class(純稚的數據類)

          它們擁有一些值域,以及用于訪問(讀寫)這些值域的函數,除此之外一無長物。

          1.21  Refused Bequest(被拒絕的遺贈)

          子類不像繼承父類的函數和數據,這往往是繼承體系的錯誤。

          如果子類復用父類的行為,但又不愿支持父類的接口,這種情況下Refused Bequest 的壞味道會很強烈。

          1.22  Comments(過多的注釋)

          注釋其實是一種香味,更多的情況下它被用作除臭劑:即代碼中出現壞味道(設計糟糕的代碼),然后用注釋“除臭”。這個時候我們應該對這些壞味道的代碼進行重構,然后,你會發現注釋變成了多余的。

          當你感覺需要注釋,請先嘗試重構,試著讓所有的注釋都變得多余——代碼本身就是自注釋的。

          注釋可以用來記述“為什么做某事”、“打算做某事”、“無十足把握的區域”,而不必記錄“怎么做”。

          posted @ 2009-10-12 20:15 常高偉 閱讀(793) | 評論 (0)編輯 收藏

          以前做過一個產品,共分為三層:平臺層,應用服務器層,應用層。其中有一個業務流程,實現是在應用層,但它那里的信息不全,需要通過應用服務器層向平臺層獲取必要的業務數據,然后通過應用服務器層控制業務流程。當時考慮這個結構的時候,主要的出發點就是業務和控制分離,將業務處理從平臺層剝離開來。當時,在具體是實施過程中,我們工程師對這種結構抵觸心理很強烈。他認為我們的業務開發非常的繁瑣,而且經常要貼“狗皮膏藥”。

          先拋開上面這個實例的設計思路,這里面反映出一個問題:軟件開發過程中,軟件體系結構同樣需要“重構”

          結合經典的《重構》,這里簡單的寫一下軟件體系結構重構的定義,原因,設計,方法。僅作拋磚引玉,希望能和大家一起思考。

          何謂重構

          對軟件體系結構的一種調整,目的是在不改變其“外在行為”的前提下,調整其結構,使其易于修改,維護和理解。

          為何重構

          1、使整個系統易于添加新的功能。為系統添加新功能將會非常的容易。

          2、調整系統中各個模塊的功能,角色,使整個系統更容易理解。
          何時重構

          由于系統結構的重構成本非常高,所以要選擇一個合適的重構時機。

          1、為系統添加功能時重構。此時項目進度壓力如果非常大,這放棄此時重構。

          2、軟件第一個版本開發完畢后重構。在第一個版本開發完畢,第二個版本開發之前,根據第一個版本的開發經驗,對系統進行重構。

          3、開發出系統原型時進行重構。開發出一個系統的原型的時候,如果發現系統需要重構,這及時的進行,這個時候重構成本較低,但對是否重構決策要求較高。

          重構的必要條件

          重構之前必須為軟件系統建立一個可靠的、自動化的功能測試環境,這樣才能有效防止重構帶來的危害。好的測試時重構的根本。重構之前,首先檢查自己是否有一套可靠的測試機制。這些測試必須有自我檢驗(selfchecking)能力。

          重構與設計

          系統架構層次的重構,因為重構的成本相對較高,所以預先設計的程度要相對較深,要盡量考慮系統可能遇到的情況,方案要適當的靈活和強固。盡量減少系統結構的重構。

          軟件體系結構的重和代碼的重構的區別

          1、針對的層次不同:一個是系統結構層次的,一個是代碼層次的。

          2、重構成本不同,系統結構的重構成本相對較高。

          3、是否重構的決策者不同。

          4、重構的時機不同。

          posted @ 2009-10-12 20:14 常高偉 閱讀(348) | 評論 (0)編輯 收藏

          在學習的不同的階段我們應該保持的心態:
          1、接觸之前,擁有一個好奇的心態。與自己原有的知識對比,不要對新的知識產生偏見。比如,原來一直采用瀑布式軟件開發,在接觸敏捷軟件開發之前,不要對敏捷軟件開發模式產生偏見:“敏捷開發根本不能夠和瀑布式開發相提并論”。偏見會阻礙新知識的學習,而是要對敏捷開發保持一種好奇心:

          1)這種理論為什么會提出來?
          2)主要為了解決什么問題?
          3)現有的理論無法解決此問題嗎?
          4)它是基于什么樣的原理?
          5)它是如何運行的?

          2、學習了一段時間,擁有一定基礎之后,要以一種批判的、懷疑的心態來對待學習的知識。當你擁有了對新知識的一定認識后,有可能會發現它很好解決了你以前遇到的一些非常棘手的問題,或者發現你找到了某一領域問題的完美的解決方案。這個時候,你要讓自己堅信,“沒有銀彈”,沒有包治百病的靈丹妙藥。任何方案、理論、技術都有其依賴的條件,或者在解決原有問題后,引入的新的問題。此時,你要以一種批判的、懷疑的態度來對待它。要搞清楚:

          1)它所依賴的前提條件是什么?
          2)應用它的時候有沒有什么假設?
          3)它在那些場景下適用、那些場景下不適用?
          4)它在解決原有問題之后,有沒有引入新的問題?

          3、當你達到專家級或頂級水平之后,應該持有一種“獨孤求敗”的心態:尋求新的挑戰,爭取“百尺竿頭更進一步”,就像獨孤求敗一樣,”闖蕩江湖,只求一敗 “:把每次的失敗看成進步的機會。同時保持敬畏、謙虛的心態,你所掌握的知識,也不是”銀彈“,也有其局限之處,或者隨著環境的發展,已經無法適應環境,需要尋求新的突破。這個時候,你要根據你豐富的實踐,提出創新的思路或知識。
          總之:
          1、在學習新知識、新領域的時候,要保持一個空杯的心態,只有這樣才能為自己敞開自我提升之路。
          2、偏見導致對新知識的排斥,教條倒是對新知識的盲從。
          3、沒有絕對的真理,也沒有絕對的謬論。絕對的真理讓我們盲從,絕對的謬論讓我們偏見。

          posted @ 2009-10-12 20:13 常高偉 閱讀(135) | 評論 (0)編輯 收藏

          1  構筑測試體系

          如果你想進行重構,首要前提就是要擁有一個可靠的測試環境。

          “編寫優良的測試程序,可以極大的提高我的編程速度,即使不進行重構也是如此。”

          1.1  自我測試代碼(Self-testing Code )的價值

          “Class 應該包含他們自己的測試代碼。”

          “每個Class 都有一個測試函數,并用它測試自己這個 Class 。”

          確保所有的測試都完全自動化,讓它們檢查自己的測試結果。

          只要寫好一點功能,就立即添加測試。

          一整組(a suite of )測試就是一個強大的“臭蟲”偵測器,能夠大大縮減查找“臭蟲”所需要的時間。

          “實際上,編寫測試代碼的最有用時機是在開始編程之前。當你需要添加特性的時候,先寫相應的測試代碼。聽起來離經叛道,其實不然。填寫測試代碼其實就是問自己:添加這個功能需要做什么。編寫測試代碼還能使你把注意力集中于接口而非實現上頭(永遠是件好事)。預先寫好的測試代碼也為你的工作按上一個明確的結束標志:一旦測試代碼運行正常,工作就可以結束了。”

          構建自我測試的代碼。

          1.2  JUnit測試框架( Testing Framew )

          頻繁的運行測試,每次編譯請把測試也考慮進去,每天至少執行每個測試一次。

          單元測試和功能測試

          “每當你接獲臭蟲提報,請先撰寫一個單元測試來揭發這只臭蟲。”——如何揭發?這里需要根據報告準確定位。單元測試會對此有幫助嗎?

          1.3  添加更多的測試

          “觀察Class 該做的所有事情,然后針對任何一項功能的任何一種可能失敗的情況,進行測試。”

          “測試應該是一種風險驅動(risk driven )行為,測試的目的是希望找出現在或未來的可能出現的錯誤。”

          “測試的訣竅是:測試你最擔心的部分。”

          這點和我目前的想法不大相同。我目前的想法是,測試要對程序做100% 的保證,所以,要測試程序可能行為的每一種情況,保證其正確性。按照我的想法,值域的設置和訪問函數也是要測試的。作者的意思是,測試代碼要用最低的成本,獲取最大的收益。這一點,要我在實際的環境中進行抉擇。

          “編寫不是十分完美的測試并實際運行,好過對完美測試的無盡等待。”——我持懷疑態度。

          運用測試用例前后執行的函數:tearDown 和 setUp ,保證測試用例之間相互隔離,而非相互影響。

          做一個懶惰的程序員——。

          考慮可能出錯的邊界條件,把測試火力集中在那兒。

          “測試(優先)可以調高編程速度”,這一點我要在實踐中驗證一下,如果真是這樣,那我就要嘗試在我們部門推行這種方法。

          “當測試達到一定的程度后,測試效益會呈現遞減態勢。”所以,你不要期望通過測試找出所有的bug ,而是要通過測試,找出絕大多數的 bug 。

          這個地方其實也符合“二八定律”:即20% 的測試可以找出 80% 的 bug ,其余的 80% 的測試可以找出剩下的 20% 的 bug 。我們要做的,就是寫這 20% 的測試,而非 100% 的測試。

          posted @ 2009-10-12 20:11 常高偉 閱讀(148) | 評論 (0)編輯 收藏

          上一篇介紹了微內核流程引擎開發背景,這篇介紹它的功能描述。

          基本功能:

          1、能夠通過腳本定義流程,更改流程。

          2、對軟交換系統應用服務器的所有的接口都可以編輯。

          3、異常處理,實現補償機制。

          4、流程要支持:順序執行,分支處理,跳轉執行。

          5、腳本中支持簡單的數據庫操作,比如:記錄查詢(根據查詢結果決定流程),字段查詢,記錄增刪改。


          擴展功能:

          1、提供多種調用形式:1)動態鏈接庫直接調用;2)socket通信調用;3)遠程調用;4)WSDL方式調用。

          2、實現一個流程引擎虛擬機。專門處理流程。

          3、支持業務以無狀態的形式開發。所有的狀態在腳本中定義。

          4、開發一個流程編輯界面。

          5、開發一個腳本編譯器,檢查腳本的錯誤。

          6、開發一個簡單的語言,實現快速流程編輯的功能。這里要實現一個編譯器,編譯結果就是流程腳本。

          7、實現一個方向編譯器,從流程腳本到流程開發語言。

          上面的這些功能有的已經實現,有的正在實現。后面我會詳細描述這些功能的設計與實現。

          posted @ 2009-10-12 20:08 常高偉 閱讀(250) | 評論 (0)編輯 收藏

          我設計的流程引擎是腳步驅動的。腳本中定義了流程執行的環境,流程操作的對象,流程執行的步驟。下面是一個流程腳本的示例:

          <?xml version="1.0" encoding="utf-8"?>
          <process name="make_call">
              <data type="user_tel">called_number</data>
               <object type="user" id="global_data:called_number" operation="must">obj_user</object>
               //用戶對象描述中,號碼是必須的,是流程引擎和業務的交互唯一標識,callid是可選的。
              <object type="user" object_num="global_data:called_number" operation="must">obj_user</object>
              <sequence name="make_call">
                  <invoke interface="make_call" node="make_call_001" object_user="obj_user" calling_number="6699" original_number="123456" call_type="local_call">

                  </invoke>
                  <invoke interface="play_voice" node="play_voice_001" object_user="obj_user" play_long="100" play_file="/home/welcome.au">

                   </invoke>   
              </sequence>
          </process>


          腳本的含義

          1、process的name屬性表示流程的名稱,用于在程序中調用。

          2、<data type="user_tel">called_number</data>表示定義了一個流程的全局外部變量。有程序在調用流程是作為流程數據傳送給流程。這個數據要在后面的流程中使用。

          3、<object>部分在流程中定義流程操作的對象。一般分為用戶和會場。這里表示是用戶。屬性“id”表示對象的唯一標識。這里引用的是流程的全局數據:global_data:called_number,也就是在上面定義的數據。屬性“operation”表示此對象是可選還是必選。如果是必須,這如果此對象被釋放,這流程也要被被結束。否則,不結束。中間的內容表示對象在流程中的唯一標示,這里是obj_user,后面的節點可以通過使用它來操作對象。

          4、<sequence>表示順序調用下面的節點。

          5、<invoke >表示調用節點。屬性“interface="make_call"”表示此節點調用的接口是make_call。make_call是在代碼中定義好的關鍵字,對應一個軟交換系統的接口。屬性“node”表示節點的唯一標識,在流程內部唯一,可以在流程跳轉的時候使用。 “object_user="obj_user"“表示make_call 接口操作的對象。有<object>創建。 calling_number="6699" original_number="123456" call_type="local_call"表示的是make_call接口調用時的數據。

          6、<invoke interface="play_voice"表示對此對象進行放音。

          這個腳本的意思是,根據流程輸入的號碼,創建用戶對象,并且發起呼叫,對用戶進行放音。


          復雜的腳步定義:

          上面的是一個簡單的示例。為了能夠實現流程編輯,要考慮很多的情況,要能夠進行分支處理,跳轉執行,捕獲事件等。

          1、分支的實現

              <recive event="user_key" ="" node="receive_key" object_user="obj_user" time_out="10"></recive>
              <switch condition_type="user_key" object="obj_user">
                      <case condition="9092">
                          <sequence name="d">
                          </sequence>
                      </case>
                      <case condition="time_out">
                          <sequence name="d">
                          </sequence>
                      </case>           
                      <otherwise>
                            <sequence name="">
                                <goto node="play_voice_001">goto_001</goto>
                            </sequence>
                      </otherwise>
              </switch>

          1)<recive event="user_key"表示接受指定用戶的按鍵。如果超過10秒為收到按鍵則認為用戶按鍵結束。

          2)<switch condition_type="user_key"表示一用戶的按鍵為分支條件,進行分支處理。

          3)<case condition="9092">表示如果用戶的按鍵式0092的話則進入此分支進行處理。

          4)<case condition="time_out">如果超時為收到用戶按鍵,這進入此分支處理

          5)<otherwise>如果上面的條件都不滿足,則進入此分支處理。


          2、跳轉的實現:

          <goto node="goto_001" next_node="play_voice_001"></goto>

          表示此節點是一個跳轉節點,要跳轉到的下一個節點是play_voice_001。


          3、信號捕獲的實現:

                 <pick name="pick_001" time_out="10">
                      <on_event event="on_ring_180" result="success" reason="normal">
                          <sequence name="008">
                          </sequence>
                      </on_event>
                      <time_out>
                          <sequence name="008">
                          </sequence>
                      </time_out>
                      <otherwise event="on_ring_180:on_ring_183">
                            <sequence name="009">
                              </sequence>
                      </otherwise>
                  </pick>

          1)<pick name="pick_001" time_out="10"><pick>活動會等待一組相互排斥事件中的一個事件的發生,然后執行與發生的事件相關聯的活動。它會阻塞業務流程執行,以等待某一特定的事件發生,比如接收到一個合適的消息或超時警報響起。當其中任何一個事件被觸發后,業務流程就會繼續執行,pick也隨即完成了,不會再等待其他事件的發生。

          2)<on_event event="on_ring_180" result="success" reason="normal">表示如果收到的on_ring_180,且結果是success,原因是normal。觸發此流程的處理。

          3)<time_out>表示超時為收到制定事件的處理。

          4)<otherwise event="on_ring_180:on_ring_183">表示收到其他的事件,比如:on_ring_180或on_ring_183,都進入此分支處理。

          posted @ 2009-10-12 20:08 常高偉 閱讀(199) | 評論 (0)編輯 收藏

          開發背景

          我們公司是主要從事企業語音方面產品的開發,主要產品比如:調度系統,指揮系統,電話會議系統,呼叫中心系統等。這些系統都有一個共同特點,就是涉及到呼叫,放音,收發按鍵,會場操作。我們的業務產品都是基于我們的軟交換系統之上構建的,軟交換系統的應用服務器向外提供這些服務。


          產生的問題

          我們在開發的過程中就發現一個問題,每個產品在此接口上都會做很多重復的開發,特別是在IVR處理上面。

          IVR (Interactive Voice Response)即交互式語音應答,可以提高呼叫服務的質量并節省費用。IVR是一種功能強大的電話自動服務系統。

          由于我們的系統重新架構,正在開發軟交換API接口,所以我們試圖解決這個問題。

          為了方便上層對底層接口的調用,減少重復開發,產品能夠針對用戶需求迅速更改,我們提出了事務流的概念。即上層程序設定一個事務流,比如:相對用戶9092發起呼叫,用戶應答后對用戶放音,讓用戶輸入密碼,密碼驗證成功加入制定會場。上層程序設定好這樣的事務流后,發送給下層進行處理。下層處理完畢后,回復處理結果。

          事務流的概念最終并沒有實現,一是我們對事務流的異常處理分析不足;二是采用事務流的概念擴展性,可維護性不是很好。

          后來我們在事務流的基礎上引進了SOA的概念,即將底層的接口封裝為一系列松散耦合的服務,在上層通過對服務的編排實現流程編輯的功能。但是實施SOA困難非常大,一是SOA沒有大規模的應用,文檔,經驗都比較少,實施風險較大;二是實施 SOA要工具的支持,需要投資。這樣的話我們寧可重復開發。

          最終這個流程編輯的功能并沒有完成,但是我們還是在API中實現了一個簡單的流程編輯,這個流程編輯是通過硬編碼來實現的,和我們的愿望相差較大。


          新的思路

          后來看了一家平臺軟件公司的介紹,他們是做企業應用平臺的。企業應用中很多的一點就是工作流。他們的平臺就是集成了一款開源的工作流引擎:JBPM。第一次接觸JBPM,感覺很多思路可以借鑒。后來在網上搜資料的時候搜到幾篇研究微內核流程引擎的文章:

          揭秘jbpm流程引擎內核設計思想及構架

          微內核過程引擎的設計思路和構架

          微內核工作流引擎體系架構與部分解決方案參考

          這幾篇文章是我設計流程引擎的核心基礎。看完之后最終決定自己設計一個微內核流程引擎。


          使用開源還是自己開發

          決定動手開發前,有兩種方式,一是使用開源產品,比如JBPM;二是自己動手開發。當時的考慮是:

          1、JBPM是用java開發,我們公司的業務產品目前基本上都是c,和java交互不便。

          2、JBPM是針對企業工作流設計的,工作流和IVR導航一個重要的區別是,IVR導航對時效性要求很高。要求系統能夠及時響應。

          3、我不是JAVA出身,當時對java還有些畏難心理。

          所以,最終決定自己用C++開發流程引擎。

          如果是現在的話,即便是決定自己開發,一會好好研究一下JBPM的源碼的。


          借鑒的其他思路

          1、數據驅動(DD)。在腳本中XML定義流程,程序啟動后將流程讀入程序,然后供上層程序調用。如果流程更改,這直接修改腳本即可。

          2、BPEL。Business Process Execution Language 的縮寫,意為業務過程執行語言 ,是一種基于XML 的,用來描寫業務過程的編程語言。我的腳步語言參考BPEL的描述。

          posted @ 2009-10-12 20:05 常高偉 閱讀(376) | 評論 (0)編輯 收藏


          posts - 19, comments - 0, trackbacks - 0, articles - 0

          Copyright © 常高偉

          主站蜘蛛池模板: 阿拉善盟| 韶山市| 峨山| 肇庆市| 濮阳市| 太白县| 宁津县| 龙里县| 涪陵区| 尤溪县| 洪湖市| 遂宁市| 抚顺市| 凤台县| 嘉祥县| 慈溪市| 徐水县| 九龙城区| 筠连县| 信阳市| 漠河县| 连山| 天全县| 二连浩特市| 营口市| 蓝田县| 八宿县| 盐山县| 邓州市| 大厂| 新野县| 卓尼县| 景德镇市| 如东县| 肥城市| 长沙市| 新野县| 蒙城县| 灌云县| 台州市| 长兴县|