原文連結:一個用C/C++分別實現介面與實現相分離的設計原則的例子
良好的設計應該只暴露介面給使用者,所有的實現細節對使用者來說應該是隱藏的,也就是說使用者只要給介面傳遞相應的參數就行了,不需要管內部是如何?的,比如我們使用fopen,fseek,CreateWindow等函數會發現很好用,而不需要管fopen,fseek,CreateWindow函數內部代碼是如何?的,資料結構是如何組織的,也就是說絕對不能暴露任何的細節給使用者,包括資料群組織在內。
我現在用C和C++舉一個例子,來說說C/C++分別是如何?的,然後來看看哪種實現更好。
先來看C++用類實現的封裝:
--------------------------- interface1.h ---------------------------
#ifndef INTERFACE1_H#define INTERFACE1_Hclass DATA{private: int _i; short _j;public: DATA(); ~DATA(); void set(int i, short j); void get(int* i, short* j); };#endif
--------------------------- interface1.cpp ---------------------------
#include "interface1.h"DATA::DATA(){ _i = _j = 0;}DATA::~DATA(){ _i = _j = 0;}void DATA::set(int i, short j){ _i = i; _j = j;}void DATA::get(int* i, short* j){ *i = _i; *j = _j;}
--------------------------- test.cpp ---------------------------
#include <stdio.h>#include "interface1.h"int main(){ DATA data; int i; short j; data.set(2, 3); data.get(&i, &j); printf("i = %d, j = %d\n", i, j); return 0;}
再來看 C 如何巧妙的封裝以及隱藏實現細節:
--------------------------- interface.h ---------------------------
#ifndef INTERFACE_H#define INTERFACE_Hvoid* data_create();void data_set(void* dummy, int i, short j);void data_get(void* dummy, int* i, short * j);void data_destroy(void* dummy);#endif
--------------------------- interface.c ---------------------------
#include <stdlib.h>struct DATA{ int i; short j;};void* data_create(){ return malloc(sizeof(struct DATA));}void data_set(void* dummy, int i, short j){ struct DATA* data = dummy; data->i = i; data->j = j;}void data_get(void* dummy, int* i, short * j){ struct DATA* data = dummy; *i = data->i; *j = data->j;}void data_destroy(void* dummy){ free(dummy);}
--------------------------- test.c ---------------------------
#include <stdio.h>#include "interface.h"int main(){ int i; short j; void* data = data_create(); data_set(data, 2, 3); data_get(data, &i, &j); printf("i = %d, j = %d\n", i, j); data_destroy(data); return 0;}
可以看的出來,C的實現只暴露了介面給使用者,內部的實現細節都隱藏了起來,可是C++用類實現反而在標頭檔暴露了實現細節。
當然用C++也可以做到只暴露介面給使用者,不過實現起來會比較複雜,而且需要消耗更多的記憶體(使用了虛函數)。
-------------------------------------- parent.h --------------------------------------
#ifndef PARENT_H#define PARENT_Hclass PARENT{public: virtual void set(int i, short j) = 0; virtual void get(int* i, short* j) = 0;};PARENT* get_child();#endif
-------------------------------------- parent.cpp --------------------------------------
#include "parent.h"#include "child.h"PARENT* get_child(){ return new CHILD;}
-------------------------------------- child.h --------------------------------------
#ifndef CHILD_H#define CHILD_H#include "parent.h"class CHILD : public PARENT{private: int _i; short _j;public: CHILD(); ~CHILD(); void set(int i, short j); void get(int* i, short* j); };#endif
-------------------------------------- child.cpp --------------------------------------
#include "child.h"CHILD::CHILD(){ _i = _j = 0;}CHILD::~CHILD(){ _i = _j = 0;}void CHILD::set(int i, short j){ _i = i; _j = j;}void CHILD::get(int* i, short* j){ *i = _i; *j = _j;}
-------------------------------------- test.cpp --------------------------------------
#include <stdio.h>#include "parent.h"int main(){ int i; short j; PARENT* parent = get_child(); parent->set(2, 3); parent->get(&i, &j); printf("i = %d, j = %d\n", i, j); return 0;}
另外:
關注這個文章:http://bbs.csdn.net/topics/310212385,摘錄幾句,當思考下:
我們知道,類有介面(成員函數),有資料、狀態變數等,這些都有實現代碼。由於介面是要向外公開的,而實現是需要隱藏的(使用者不需要知道),這樣才能應對變化。比如介面的實現有變化,或者一個介面有多種可能的實現,我們就可以隨意修改這些實現,而不影響使用者的使用,因為使用者看到的只是對外公開的介面,介面並沒有變。
將介面與實現分離的技術:
(1)Interface class:將介面部分實現為abstract base class,在C++中,這個抽象類別中含一個virtual解構函式和一組pure virtual函數,實現部分則由派生出來的各個子類來完成。C++/Java/C#中都有現成的abstract類機制,例子就不需要舉了。
(2)Handle class:也稱為pimpl技術,就是把隸屬對象的資料(即實現)從原對象中抽離出來,封裝成一個獨立的impl對象,在原對象中用一個指標成員指向它,一般使用智能指標。舉一個例子,按鈕組件通常都有一個背景映像作為資料,通常這個映像作為組件類的一個成員,為了分離實現,我們把這些資料抽離出來進行獨立的封裝:
struct PMImpl{ //封裝資料對象的 std::tr1::shared_ptr<Image> bgImage; //指向背景映像的智能指標 int imageChanges; //背景映像更改次數}; class Button{ //按鈕類private: Mutex mutex; //互斥鎖 std::tr::shared_ptr<PMImpl> pImpl; //指向資料對象的智能指標public: void changeBackground(std::istream &imgSrc); //...}; void Button::changeBackground(std::istream &imgSrc){ using std::swap; //使用這個函數進行異常安全編程 Lock m1(&mutex); //加鎖,下面成為臨界區 std::tr1::shared_ptr<PMImpl> pNew(new PMImpl(*pImpl)); //建立臨時的pNew, //並指向了原有的資料對象(含有背景映像) pNew->gbImage.reset(new Image(imgSrc)); //根據傳進來的映像,更改背景映像 ++pNew->imageChanges; //更改次數加1 swap(pImpl,pNew); //把更改後的資料交換到pImpl中,並且會釋放互斥鎖mutex, //從而完成了Button背景映像的更改}
介面主要是用於模組間的互動,實現就是具體的處理邏輯;如果介面過多的牽扯處理邏輯,代碼的重用性會比較差;如果介面和實現分開,新增功能只需要關注具體的實現,不需要改動介面。
公有介面是類的抽象組件,類函數定義是實現細節,把兩者分離,具體做法就是類成員函數定義與公有介面分屬不同的代碼檔案。
介面與實現的分離其實你一直在使用,最直接的例子就是標準庫,平時你使用標準庫的標頭檔,這個就是個介面,而標準庫被封裝在不同的地方,例如靜態庫,這個就是實現。兩者是分離的。
介面和實現分離,模組間的依賴會自然轉化為對介面資料類型的依賴