1. 程式人生 > >介面與實現分離

介面與實現分離

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

我們在使用面嚮物件語言程式設計時,或者更寬泛些 ,設計一個好的介面時,經常會把 介面與實現分離這句話掛在嘴邊。只是,真正弄明白這句話的含義怕是比聽到這句話晚好幾年。因為沒有足夠的專案經驗和知識積累,你很難對這句話有真實的體會。

什麼是介面

這個問題有些千人千面,如果你聽過萬物皆是物件,那麼我可以告訴你,萬物皆介面,嗯,有點拾人牙慧的意思,在計算機的世界裡,你(coder)寫的任何一個字元都是介面,它連線著計算機和現實世界;當然,太抽象了,等於沒說。我們往大點說,介面就是一組程式碼和另一組程式碼的橋樑。再往大點說,介面是一個程式對另一個程式的連線點;繼續大點說,介面是一個程式設計師和另一個程式設計師溝通的工具。再往大點說,介面就是,嗯,停下來好了。

定義一個好的介面是非常重要的,如果你的介面物件是計算機,你需要寫出計算機能識別和良好構建(至少是編譯器級別的)的程式碼;如果你的介面是另一組程式碼,那你需要做到良好的定義,介面可以是一個函式,一組api,一個類等;如果你的介面物件是程式設計師,那除了易讀的程式碼本身外,你可能還需要一些說明文件。

本文把介面侷限在程式碼層面,類或者函式層面。

什麼是實現

限定了介面的範圍,我們說說實現,實現從本質上說就是把承若的類或者函式給coding done了。作為程式設計師,我們大部分工作都是在實現。

介面和實現分離:

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,現在已經有點工廠模式的味道了。

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