也許,你聽過設計模式裏的箴言,要針對接口編程,而不該該針對細節(具體)編程;或者你還聽過,要減小代碼間的依賴關係;甚至你還知道,修改一個模塊時,要保證從新編譯的文件依賴要最小化,而從新編譯的時間最短化。當你問,How to?大神就會祭出嗯,你應該將接口與實現分離
的經文。java
咱們在使用面嚮對象語言編程時,或者更寬泛些 ,設計一個好的接口時,常常會把 接口與實現分離
這句話掛在嘴邊。只是,真正弄明白這句話的含義怕是比聽到這句話晚好幾年。由於沒有足夠的項目經驗和知識積累,你很難對這句話有真實的體會。c++
這個問題有些千人千面,若是你聽過萬物皆是對象,那麼我能夠告訴你,萬物皆接口,嗯,有點拾人牙慧的意思,在計算機的世界裏,你(coder)寫的任何一個字符都是接口,它鏈接着計算機和現實世界;固然,太抽象了,等於沒說。咱們往大點說,接口就是一組代碼和另外一組代碼的橋樑。再往大點說,接口是一個程序對另外一個程序的鏈接點;繼續大點說,接口是一個程序員和另外一個程序員溝通的工具。再往大點說,接口就是,嗯,停下來好了。程序員
定義一個好的接口是很是重要的,若是你的接口對象是計算機,你須要寫出計算機能識別和良好構建(至少是編譯器級別的)的代碼;若是你的接口是另外一組代碼,那你須要作到良好的定義,接口能夠是一個函數,一組api,一個類等;若是你的接口對象是程序員,那除了易讀的代碼自己外,你可能還須要一些說明文檔。編程
本文把接口侷限在代碼層面,類或者函數層面。設計模式
限定了接口的範圍,咱們說說實現,實現從本質上說就是把承若的類或者函數給coding done了。做爲程序員,咱們大部分工做都是在實現。api
然咱們看一段代碼:
假如咱們須要實現一個學生類 ,它有學號、出生日期、寢室號等信息。生日日期咱們使用Date類,寢室號咱們使用DormNum類。函數
class Student { public: Student(const Date& birthDate,const DormNum& bedroomNum,const std::string& name); std::string birthDate() const; std::string bedroomNum() const; std::string name() const; private: Date birthDate_; DormNum bedroomIn_; std::string name_; };
爲了讓這個類能經過編譯,咱們須要將使用到的類的定義經過include包含進來,就像這樣:工具
#include <string> //包含類string #include "date.h"//包含類Date #include "dormNum.h"//包含類DormNum
這樣看來,一切都很完美,但是,一旦Date或者DormNum類發生了改變,整個Student類以及調用這個類的相關部分所有須要從新編譯,這真不是個好事情,畢竟時間寶貴啊。設計
所以,咱們須要將編譯依賴下降,使用接口和實現分離的方式來縮減須要從新編譯的代碼。指針
這個問題其實很大,我這裏只能給些思路和建議:
首先,咱們主要是擔憂Date類和DormNum類所在的文件發生改變,那麼咱們就把這兩個類單獨拿出來好了。像如今這樣:
#include <string> class Date; class DormNum; class Student { public: Student(const Date& birthDate,const DormNum& bedroomNum,const std::string& name); std::string birthDate() const; std::string bedroomNum() const; std::string name() const; private: Date birthDate_; DormNum bedroomIn_; std::string name_;
也就是說,將使用的依賴類進行前置聲明,避免編譯時沒法找到對應類的錯誤,而不用 Student類定義的文件中包含Date和DormNum的頭文件。在C++中,前置聲明一個類,而後使用這個類類型聲明其餘數據是能夠的,只是,這樣作還不夠,由於當咱們須要調用這個類的某個成員函數,到底仍是須要類的成員函數的定義。所以,咱們能夠準備兩個頭文件,一個用於安放類的前置聲明,好比studentFwd.h
;另外一個則是具體的類的定義,就像最開始的date.h,dormNum.h之類的頭文件。
好比放前置聲明的studentFwd.h
class Date; class DormNum;
接着,咱們開始把實現剝離出來:
接口定義好了,咱們開始實現一個StudentImpl類,全部的Student類的具體工做都將在這個類中完成,
class StudentImpl { public: StudentImpl(const Date& birthDate,const DormNum& bedroomNum,const std::string& name); std::string birthDate() const; std::string bedroomNum() const; std::string name() const; private: Date birthDate_; DormNum bedroomIn_; std::string name_; };
事實上StudetImpl擁有和原Student類徹底同樣的結構,修改改變的是Student類:
class Date; class DormNum; class Student { public: Student(const Date& birthDate,const DormNum& bedroomNum,const std::string& name) :spStuImp(new StudentImpl(birthDate,bedroomNum,name)); std::string birthDate() const{ return spStuImp->birthDate(); } std::string bedroomNum() const{ return spStuImp->bedroomNum(); } std::string name() const{ return spStuImp->name(); } private: shared_ptr<StudentImpl> spStuImp; };
做爲接口類Student,將它的具體執行所有調用StudentImpl類去完成,本身則保持對外穩定的接口形式,這是接口與實現分離的雛形。回到編譯上,當外界依賴的類改變後,Student類不須要從新編譯,惟一須要作的是StudentImpl的從新編譯。
不過,咱們還可使用真正意義上的接口類來完成這個目標,在java中有明確的interface聲明,儘管C++中並無該關鍵字,可是能夠在形式上與之保持一致。
class Student { public: virtual std::string birthDate() const = 0; virtual std::string bedroomNum() const = 0; virtual std::string name() const = 0; virtual ~Student(); };
如今的Student被定義爲純虛類,咱們使用一個具體的類來繼承它,並在此類中實現具體的接口操做
class RealStudent:public Student { public: RealStudent(const Date& birthDate,const DormNum& bedroomNum, const std::string& name) :birthDate_(birthDate),bedroomIn_(bedroomNum),name_(name) {} virtual ~RealStudent(); virtual std::string birthDate() const ; virtual std::string bedroomNum() const ; virtual std::string name() const ; private: Date birthDate_; DormNum bedroomIn_; std::string name_; };
接口使用的時候,咱們可使用Student指針指向具體的子類對象,好比
Student *reStudent = new RealStudent;
或者更爲合理的智能指針:
shared_ptr<Student> Student::create(const Date& birthDate, const DormNum& bedroomNum,const std::string& name){ return shared_prt<Student>(new RealStudent(birthDate,bedroomNum,name)); }
enn,如今已經有點工廠模式的味道了。
接口與實現分離是整個設計模式大廈的最初目標,隨着軟件技術的不斷髮展,該理論的侷限性也在縮小,該思路配合軟件設計的分層理論,構建了現代軟件開發的基石。