1. 程式人生 > >外掛及可擴充套件性的理解

外掛及可擴充套件性的理解

外掛其實就是基於動態庫的軟體擴充套件技術;
http://blog.csdn.net/libbyliugang/article/details/1666006
外掛技術有三個核心:動態庫技術,面向介面程式設計技術,執行時物件查詢和生成.

動態庫技術:
    一個外掛包就是一個動態庫,每個動態庫中可以匯出很多個不同的介面的外掛.每個匯出的物件就是所謂的外掛,沒什麼神奇的.

面向介面程式設計技術:
    外掛的介面介面必須保持自始至終保持一致,即對於外掛的不同版本通過名字查詢到的外掛物件,其介面應該保持一致.基本上外掛釋出式只需要一個頭檔案和一個動態庫就夠了.

執行時物件查詢和生成:
    也就是執行時載入動態庫,然後生成通過動態庫的介面生成外掛物件.然後在現有系統中使用這個物件.


--------------------------------------
例子:
外掛介面:
class IOutput
{
public:
    virtual void output(const char* pstr) = 0;
    virtual void*getExt(const char* pextname, void* pargs) = 0;
};

匯出外掛包中的外掛的函式:
extern "C" __declspec(dllexport) 
void* createOutputObject(const char* pname, void* pargs);
這個函式要求使用者使用pname引數來指定使用者希望建立的物件;


實現外掛:
//輸出到終端的外掛:
class CTermOutput
{
...
public:
    virtual void output(const char* pstr)
   {
        ...
        printf("%s", pstr);
   }
    virtual void* getExt(const char* pextname, void* pargs)
   {
        return (NULL);
   }
};

//再實現一個輸出到檔案的外掛:
class CFileOutput
{
...
   FILE* m_pFile;
public:
    virtual void output(const char* pstr)

   {
        ...
        fprintf(m_pFile, "%s", pstr);
   }
    virtual void* getExt(const char* pextname, void* pargs)
   {
        return (NULL);
   }
};

//實現匯出這兩個不同的物件(當然這裡的例子是介面是相同的)
extern "C" __declspec(dllexport) 
void* createOutputObject(const char* pname, void* pargs)
{
    ...
    if (0 == strcmp("TermOutput", pname))
   {...
        return new CTermOutput;
   }
    else
    {
        return new CFilePutput;
    }
    return (NULL);
};


//使用者怎麼使用呢?
IOutput* po = (IOutput*)createOutputObject("TermOutput", NULL);
IOutput* po = (IOutput*)createOutputObject("FileOutput", NULL);
這裡的程式碼的特點:使用者不必要知道createOutputObject創建出來的物件是怎麼實現的,只需要知道物件的名字就可以了,由於名字就是個字串, 所以釋出外掛的時候只需要提供外掛的名字,整合這個外掛的軟體就可以使用了.這就是執行時物件的查詢和生成.
由於一般的軟體處理字串是相當容易的,所以這就導致基於外掛的軟體很容易修改,線上修改軟體(Online update)也沒問題.

--------------------------------------
匯出不同的介面外掛,方便升級,而且保持相容性:
軟體升級,而又要保持相容性是比較難以處理的.然是如果採用這裡介紹的外掛技術就變得方便多了.
軟體升級過程中往往涉及到,介面的修改,但是介面一旦修改而又要就新的釋出包在舊系統中能夠很好地執行就比較難處理了.
這裡採用外掛形式的可擴充套件介面,就可以保證升級後舊的業務可以較好執行,而新業務可以使用新功能,新介面.

以程式碼為例;
class IOutput
{
public:
    virtual void output(const char* pstr) = 0;
    virtual void*getExt(const char* pextname, void* pargs) = 0;
};
這裡的getExt()成員函式具有與前面講到的createOutputObject的類似的介面,所以他具有和createOutputObject一樣的性質----通過名字查詢生成物件.
加入由於業務的擴充套件,我們需要在原有介面上增加一個新函式:
virtual void* format(const char*, int, int) = 0;
由於就為了相容就業務就不能直接在原有介面中增加新函式,怎麼辦呢?
提供一個新的介面:
class ITermOutputExt
{
public:
    virtual void* format(const char*, int, int) = 0;
    virtual void*getExt(const char* pextname, void* pargs) = 0;//新的介面最好也支援這個擴充套件函式,誰能保證這個新的介面不會再增加新函式呢?
};

然後CTermOutput的釋出包中的getExt提供下面的實現:
void* CTermOutput::getExt(const char* pname, void* pargs)
{...
    if (0 == strcmp(pname,"TermOutputExt")
    {
        return (new CTermOutputExt(pargs));   //  pargs可能就是 IOutput, 具體實現成什麼樣這裡就不好說了,場景不同實現方式不同;
    }
}

這樣新業務的開發者就可以這樣用這這個新介面:
pOutputExt = pOutput->getExt("TermOutputExt", pOutput);
pOutputExt->format(...);

--------------------------------------
讓createOutputObject()匯出不同的介面:
由於createOutputObject()的作用是根據名字查詢並生成物件,細心的同志可以看到這個函式的返回值是void*,
這意味著他可以匯出其他的介面,而不限制在僅僅到處IOutput這個一個介面上;


--------------------------------------
使用外掛設計系統時應該注意的問題:
1.效能問題:一個大的系統如果要求對啟動速度很高,比冷雙機系統,那麼不推薦對系統大範圍使用外掛,
   經過測試如果系統大面積使用外掛,那麼系統的速度將大打折扣,啟動時間往往會延長好幾倍甚至幾十倍!
  主要原因是載入外掛時初始化動態庫是比較慢的,少量還好,幾百個動態庫初始化起來就太慢太慢了.

2.這裡介紹的都是機遇C++的外掛的介面,但是這個外掛當然不適合使用Java語言或者basic來呼叫了.
   怎樣讓C++的外掛介面在其他語言中也可正常超出了這裡討論的範圍了.