接口與實現分離

也許,你聽過設計模式裏的箴言,要針對接口編程,而不該該針對細節(具體)編程;或者你還聽過,要減小代碼間的依賴關係;甚至你還知道,修改一個模塊時,要保證從新編譯的文件依賴要最小化,而從新編譯的時間最短化。當你問,How to?大神就會祭出嗯,你應該將接口與實現分離的經文。java

咱們在使用面嚮對象語言編程時,或者更寬泛些 ,設計一個好的接口時,常常會把 接口與實現分離這句話掛在嘴邊。只是,真正弄明白這句話的含義怕是比聽到這句話晚好幾年。由於沒有足夠的項目經驗和知識積累,你很難對這句話有真實的體會。c++

什麼是接口

這個問題有些千人千面,若是你聽過萬物皆是對象,那麼我能夠告訴你,萬物皆接口,嗯,有點拾人牙慧的意思,在計算機的世界裏,你(coder)寫的任何一個字符都是接口,它鏈接着計算機和現實世界;固然,太抽象了,等於沒說。咱們往大點說,接口就是一組代碼和另外一組代碼的橋樑。再往大點說,接口是一個程序對另外一個程序的鏈接點;繼續大點說,接口是一個程序員和另外一個程序員溝通的工具。再往大點說,接口就是,嗯,停下來好了。程序員

定義一個好的接口是很是重要的,若是你的接口對象是計算機,你須要寫出計算機能識別和良好構建(至少是編譯器級別的)的代碼;若是你的接口是另外一組代碼,那你須要作到良好的定義,接口能夠是一個函數,一組api,一個類等;若是你的接口對象是程序員,那除了易讀的代碼自己外,你可能還須要一些說明文檔。編程

本文把接口侷限在代碼層面,類或者函數層面。設計模式

什麼是實現

限定了接口的範圍,咱們說說實現,實現從本質上說就是把承若的類或者函數給coding done了。做爲程序員,咱們大部分工做都是在實現。api

接口和實現分離:

1.爲什麼分離

然咱們看一段代碼:
假如咱們須要實現一個學生類 ,它有學號、出生日期、寢室號等信息。生日日期咱們使用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類以及調用這個類的相關部分所有須要從新編譯,這真不是個好事情,畢竟時間寶貴啊。設計

所以,咱們須要將編譯依賴下降,使用接口和實現分離的方式來縮減須要從新編譯的代碼。指針

2.如何分離

這個問題其實很大,我這裏只能給些思路和建議:

首先,咱們主要是擔憂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;

接着,咱們開始把實現剝離出來

1.使用implement

接口定義好了,咱們開始實現一個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的從新編譯。

2.使用abstract class

不過,咱們還可使用真正意義上的接口類來完成這個目標,在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,如今已經有點工廠模式的味道了。

接口與實現分離是整個設計模式大廈的最初目標,隨着軟件技術的不斷髮展,該理論的侷限性也在縮小,該思路配合軟件設計的分層理論,構建了現代軟件開發的基石。

相關文章
相關標籤/搜索