天天看點

程式設計核心概念:子產品化、封裝、抽象:接口與實作分離

作者:小智雅彙

在計算機科學中,子產品化是一個很重要的一個概念,子產品化可以将一個複雜系統分而治之,計算機硬體和軟體都講求子產品化,子產品化可以實作隐藏複雜的内部細節而隻暴露給使用者易于使用的簡單的接口部分,同時,子產品化也實作了一種抽象。抽象是指隻向外界提供關鍵資訊,并隐藏其背景的實作細節,即隻表現必要的資訊而不呈現細節。

比如一台電視機,您可以打開和關閉、切換頻道、調整音量、添加外部元件(如喇叭、錄像機、DVD 播放器),但是您不知道它的内部實作細節,也就是說,您并不知道它是如何通過纜線接收信号,如何轉換信号,并最終顯示在螢幕上。是以,我們可以說電視把它的内部實作和外部接口分離開了,您無需知道它的内部實作原理,直接通過它的外部接口(比如電源按鈕、遙控器、聲量控制器)就可以操控電視。

自然,子產品化需要由封裝來實作。

在C中,函數是一種功能封裝,通過将作為接口的函數聲明放在頭檔案中,将函數的實作(定義)放在源檔案中,來實作接口與實作的分離,由此,也就實作了一種功能抽象,使用函數的使用者隻需面對頭檔案中函數聲明,來了解其如何使用,函數的實作部分隐藏在源檔案中,函數的使用者可以無須理會,由函數的實作者去實作。例如,您的程式可以調用 sort() 函數,而不需要知道函數中排序資料所用到的算法。實際上,函數排序的底層實作會因庫的版本不同而有所差異,隻要接口不變,函數調用就可以照常工作。

C++的類實作了一種資料抽象,其封裝不僅能夠将資料和操作這此資料的函數綁定到一起,還具有保護類的成員通過通路說明符進行修飾而不被随意通路的能力。通過把類的實作細節設定為private,我們就能完成類的封裝。封裝實作了類的接口和實作的分離。

類封裝有兩個優點:一是確定使用者代碼不會無意間破壞對象的狀态;二是封裝的類的具體實作細節可以随時改變,而無須調整使用者級别的代碼。

一旦把資料成員定義成private,類的作者就可以比較自由地修改資料了。當實作部分發生改變時,隻需要檢查類的代碼本身以确認這次改變有什麼影響,也就是隻要類的接口不變,使用者代碼就無須改變。如果資料是public的,則所有使用了原來資料成員的代碼都可能失效。這時我們必須定位并重寫所有依賴于舊版本實作的代碼,之後才能重新使用該程式。

把資料成員的通路權限設成private還有另外一個好處,能夠防止由于使用者的原因造成資料被破壞。如果我們發現有程式缺陷破壞了對象的狀态,則可以在有限的範圍内定位缺陷,因為隻有實作部分的代碼可能産生這樣的錯誤。是以,将錯誤的搜尋限制在有限範圍内将能極大地簡化更改問題及修正程式等工作。

作為接口的一部分,構造函數和一部分成員函數應該定義在public說明符之後,以便于使用者在類的外部通路。而資料成員和作為實作部分的函數應該跟在private說明符之後,以避免使用者程式不經意間修改和破壞它們。

自然資料抽象(抽象資料類型,類)相對于功能抽象(函數),是更高一層的抽象,除了實作隐藏,還有通路權限、建立時自動初始化和基于作用域的資源釋放的概念。

C++類的成員函數的實作放在源檔案中,而類的定義放在頭檔案中,頭檔案暴露給使用類的使用者,而使用者無須關注作為實作部分的源檔案。

在 C++中,我們使用通路标簽(private、public)來加強類的封裝與抽象,相對于C使用頭檔案和源檔案來隔離接口與實作,顯然前者有更強的封裝性。使用公共标簽定義的成員都可以通路該程式的所有部分。一個類型的資料抽象視圖是由它的公共成員來定義的。使用私有标簽定義的成員無法通路到使用類的代碼。私有部分對使用類型的代碼隐藏了實作細節。

把代碼分離為接口和實作。是以在設計元件時,必須保持接口獨立于實作,這樣,如果改變底層實作,接口也将保持不變。在這種情況下,不管任何程式使用接口,接口都不會受到影響,隻需要将最新的實作重新編譯即可。

#include <iostream>
using namespace std;

class Adder{
public:
    // 構造函數
    Adder(int i = 0)
    {
        total = i;
    }
    // 對外的接口
    void addNum(int number)
    {
        total += number;
    }
    // 對外的接口
    int getTotal()
    {
        return total;
    };
private:
    // 對外隐藏的資料
    int total;
}; // 類的定義可以放到頭檔案中,類的成員函數也可以在類外定義,放到源檔案中
int main( )
{
    Adder a;
    
    a.addNum(10);
    a.addNum(20);
    a.addNum(30);
    
    cout << "Total " << a.getTotal() <<endl;
    return 0;
}
           

ref:

https://www.runoob.com/cplusplus/cpp-data-encapsulation.html

-End-

繼續閱讀