為什麼要有介面?
介面就是一個程式與其它程式交流的視窗。就比如有一個電視機,我並不需要知道它是怎樣工作的,我只要知道按電源鍵就可以開啟電視,按節目加(+)減(-)可以切換電視訊道就可以了。
Java程式設計師都知道Java中有interface可以實現對外的介面,但C++並沒有介面這樣的語法,那它要好怎樣實現對外提供介面呢?我們可以通過純虛擬函式定義一個抽象類,專門用來宣告一個類的功能。
我們完成了一個程式模組的開發,要把這個程式模組給別人用,你肯定不會把原始碼給他(那別人就完全撐屋你的技術了),你會把這個程式模組編譯成一個庫(靜態庫lib或動態庫dll)再給別人用。那別人拿到你的庫後怎樣用呢?這就需要看你的程式所提供的介面。C++的封裝性是特別好的(個人覺得比Java好多了,Java打成的jar包很容易就可以被反編譯,C++要反編譯就困難多了),我只要給你編譯出的庫和介面的標頭檔案就可以了。
從一個例項講講實現方案
需要
我們先來看一個場景。假設有一個電子文件(Document)、一個文件下有多個頁(Page),每個頁下有多個文字單元(TextUnit,表示文件內元素的基本單位),一個文件中的所有文字單元物件都有唯一的ID。其類圖關係如下:
圖1 :類的關係圖
設計
根據需求,我們可以定義三個類Document、Page、TextUnit分別表示文件、頁、文字單元,每個類我們還需要一個對外的介面,於是需要三個對外的介面類IDocument、IPage、ITextUnit。
根據這些類我們先建立.cpp檔案和.h檔案,組織一下工程(EBook)目錄結構如下:
圖2: 工程目錄結構
這裡Document、Page、TextUnit就是具體的實現類,IDocument、IPage、ITextUnit就是對外提供的介面,這樣就實現了實現與介面分離。
程式碼實現
IDocument.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#pragma once class IPage; class IDocument { public: virtual ~IDocument(void){} public: //--------------------------------------------------------------- //function: // GenerateId 生成本文件內唯一的文字物件ID //Access: // virtual public //Parameter: //Returns: // int - 返回ID //Remarks: // ... //author: luoweifu //--------------------------------------------------------------- virtual int GenerateId() = 0; //--------------------------------------------------------------- //function: // AddPage 新增一頁 //Access: // virtual public //Parameter: //Returns: // IPage* - 返回頁物件 //Remarks: // ... //author: luoweifu //--------------------------------------------------------------- virtual IPage* AddPage() = 0; }; |
IPage.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#pragma once class ITextUnit; class IPage { public: virtual ~IPage(void){} public: //--------------------------------------------------------------- //function: // AddTextUnit 新增一個文字單元 //Access: // virtual public //Parameter: //Returns: // ITextUnit* - 文字單元物件 //Remarks: // ... //author: luoweifu //--------------------------------------------------------------- virtual ITextUnit* AddTextUnit() = 0; }; |
ITextUnit.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#pragma once class ITextUnit { public: ~ITextUnit(void){} public: //--------------------------------------------------------------- //function: // GetId 獲得ID //Access: // virtual public //Parameter: //Returns: // int - 返回ID //Remarks: // ... //author: luoweifu //--------------------------------------------------------------- virtual int GetId() = 0; //--------------------------------------------------------------- //function: // SetId 設定ID //Access: // virtual public //Parameter: // [in] int id - 要設定的ID //Returns: // void - //Remarks: // ... //author: luoweifu //--------------------------------------------------------------- virtual void SetId(int id) = 0; }; |
提供C介面
從上面的程式碼我們可以看到IPage可以由IDocument建立,ITextUnit可以由IPage建立。那問題來了,IDocument由誰來建立呢?這時我們可以提供兩個全域性的函式CreateDoc和DestroyDoc用來建立和銷燬IDocument的物件指標,這兩個函式是全域性函式(C型別的函式),我們需要為其提供C的匯出介面(這很重要)。其介面定義如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
#pragma once #include "IDocument.h" #include "IPage.h" #include "ITextUnit.h" //=============================================================== //要匯出靜態庫時,匯出庫的工程要加預編譯巨集STATIC_LIBRARY,使用庫的工程什麼也要加STATIC_LIBRARY巨集 //要匯出動態庫時,匯出庫的工程要加預編譯巨集EXPORT,使用庫的工程什麼也不用加 //=============================================================== #ifdef EXPORT //匯出庫 #define _API_ __declspec(dllexport) #else //匯入庫 #define _API_ __declspec(dllimport) #endif //EXPORT #ifdef STATIC_LIBRARY //匯出靜態庫 #define EBAPI int #else //匯出動態庫 #define EBAPI extern "C" _API_ int #endif //STATIC_LIBRARY //--------------------------------------------------------------- //function: // CreateDoc 建立Document物件 //Access: // public //Parameter: // [in] IDocument * & pDocument - //Returns: // EBAPI - //Remarks: // ... //author: luowf[/luoweifu] //--------------------------------------------------------------- EBAPI CreateDoc(IDocument*& pDocument); //--------------------------------------------------------------- //function: // DestroyDoc 銷燬一個Document物件 //Access: // public //Parameter: // [in] IDocument * pDocument - //Returns: // EBAPI - //Remarks: // ... //author: luowf[/luoweifu] //--------------------------------------------------------------- EBAPI DestroyDoc(IDocument* pDocument); |
使用庫
我們可以將EBook編譯成一個靜態庫,然後再建立一個新的工程使用它。EBook工程設定:
圖3: 工程配置(說明:上圖中紅色框中的EXPORT_STATIC已重新命名為STATIC_LIBRARY的)
建立一個新的工程UseEBook使用EBook庫。UseEBook工程配製:
Generation PropertiesC++PreprocessPreprocess Definitions:STATIC_LIBRARY
Generation PropertiesLinkerGeneralAddtional Library Directories:lib庫所在路徑
Generation PropertiesLinkerInputAddtional Dependencies:EBook.lib
測試程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include "stdafx.h" #include <iostream> int _tmain(int argc, _TCHAR* argv[]) { IDocument* pDoc = NULL; if(CreateDoc(pDoc) != 0) { return -1; } IPage* pPage = pDoc->AddPage(); ITextUnit* pTextUnit = pPage->AddTextUnit(); std::cout << pTextUnit->GetId() << std::endl; DestroyDoc(pDoc); return 0; } |