1. 程式人生 > >《API Design for C++》讀書筆記(二):API特徵

《API Design for C++》讀書筆記(二):API特徵

目錄

本章所講的內容都是在回答下面這個問題:優質的API應該具有哪些基本特徵?

1、問題域建模

編寫API的目的是解決特定的問題或是完成具體的任務。因此,API應該首先為問題提供一個清晰的解決方案,同時能對實際的問題域進行準確的建模。

1.1 提供良好的抽象

API應該對它所解決的問題提供邏輯抽象。也就是說,在設計API時,應該闡述在選定問題域內有意義的深層概念,而不是公開底層實現細節。

1.2 關鍵物件的建模

API同樣需要對問題域的關鍵物件建模。該過程旨在描述特定問題域中物件的層次結構,因此經常被稱作“面向物件設計(OOD)”或者“物件建模”。物件建模的目的是確定主要物件集合(如一個播放器的主要物件可能有容器物件,解碼器物件,輸出渲染物件),這些物件提供的操作以及物件之間的關係。

2、 隱藏實現細節

建立API的主要原因是隱藏所有的實現細節,以免將來修改API對已有客戶造成影響。因此API最重要的特徵就是要切實達到這一目標。

2.1 物理隱藏: 宣告與定義

在c/c++中,宣告和定義是有特定含義的精確術語。宣告只是告訴編譯器一個名字以及它的型別,並不為其分配任何記憶體。與之相對,定義提供了型別結構體的謝姐,如果是變數則為其分配記憶體。(c程式設計師所使用的術語“函式原型”與術語“函式宣告”是等價的。)例如,一下都是宣告:

extern int i;
class MyClass;
void MyFunc(int value);

而以下都是定義:

int
i = 0; class MyClass { public: float x,y,z; }; void MyFunc(int value) { printf("In MyFunc(%d)\n", value); }

這種物理隱藏的具體做法就是,在.h檔案中宣告API的介面,類,變數等,在.c/cpp中實現這些介面,類等。

2.2 邏輯隱藏:封裝

封裝(面向物件中的一個概念)提供了限制訪問物件成員的機制(我的理解:非面嚮物件語言如c,也一樣可以實現封裝(如c中的結構體,void*指標,模組等),只是具體做法會有些差異而已,但在思想和目的上都是一樣的)。
在封裝的基礎上又會有如下一些可能需要隱藏的東西:
- 隱藏成員變數:就是不能讓外部直接訪問類的成員變數(也就是將其放入private欄位)
- 隱藏實現方法:一個類中可能會將一個API的實現劃分為幾個步驟或子模組並將其封裝成不同的子函式,而這些子函式通常是不希望外部能夠直接訪問的(也就是將這些子函式放入private欄位)
- 隱藏實現類:和隱藏實現方法同樣的道理,可能一個API類會使用幾個子類來共同實現,而這些實現也是不希望外部可以訪問和使用的(也就是說應該將其放入private欄位)

3、最小完備

優秀的API設計應該是最小完備的。即它應該儘量簡潔,但不要過分簡潔。

3.1 不要過度承諾

API中每一個共有元素(類,介面等)都是一項承諾,它承諾了該功能在API的生命週期中都像得到支援。雖然你可以背棄某項承諾,但是此舉會令客戶受挫並迫使其重寫程式碼。更糟糕的是,由於你無法保證API的穩定迫使使用者不停的修正程式碼,或者由於你移除了支援其獨特用例的功能導致他們無法使用API,那麼使用者很可能會棄用你的API。

3.2 謹慎新增虛擬函式

繼承(將某個成員函式設定為虛擬函式)暴露出的功能可能會超出預期。而且這種方式並不容易察覺。
雖然繼承十分強大(比如客戶可以通過繼承API中的類以實現客戶自己的某些功能或流程),但仍要意識到其潛在的隱患:
- 對基類看似無害的修改可能會給客戶帶來不利影響;
- 客戶可能會以你意想不到的方式使用API;
- 客戶可能採用不正確或易於出錯的方式擴充套件API;
- 重寫函式可能破壞類的內部完整性(比如某個虛擬函式的預設實現會更新類的某些狀態,但子類中的實現卻未去更新這些狀態,那樣就會出現異常);
- 虛擬函式的呼叫是在執行時查詢虛表決定具體該呼叫哪一個函式,這樣就比普通函式呼叫慢(當然在呼叫不頻繁的情況下可以忽略不計);
- 使用虛擬函式需要維護虛表,進而增大了物件的大小;
- 新增、重排或移除虛擬函式都會破壞二進位制相容性,因這都打亂了虛擬函式在虛表中的位置;
- 不是所有虛擬函式都能內聯,因而將虛擬函式宣告為內聯是沒有任何意義的;
- 過載虛擬函式需要一定的技巧(因為基類中的一組過載的虛擬函式會被子類中的一個覆蓋函式鎖隱藏)。

3.3 便捷API

簡化API是一項困難的任務。在減少API函式數目與是API易於各種客戶使用之間存在天然的矛盾(即,是讓API簡潔易用,還是讓API足夠詳細,功能多樣)。

4、 易用性

優秀的API設計應該讓簡單的任務更簡單,使人一目瞭然。當然,不能因此就忽視優秀的支援文件的重要性。事實上,這應該讓編寫文件的工作變得更容易。

使API易於理解的各方面的技巧如下:
- 可發現性(discoverable):可發現的API要求使用者能夠通過API明白如何使用它們,而不需要參閱任何解釋或文件。雖然可發現性並不一定能帶來易用性,但一般而言,可發現性有益於產生更加好用的介面。
- 不易誤用:優秀的API不僅易於使用,而且還要不易誤用。最常見的誤用就是像方法傳遞錯誤的引數或非法值。
- 一致性: 優秀的API應該採用一致性的設計方法,以便於使用者記住其風格,進而更容易被使用者採用(如:命名約定,引數順序,標準設計模式的使用,記憶體模型語義,異常使用,錯誤處理等 )

  • 正交:在API設計中,正交性意味著方法沒有副作用。呼叫設定特定屬性的方法應該僅改變那個屬性而不能額外改變其他可以公共訪問的屬性。
  • 健壯的資源分配:記憶體管理是c/c++程式設計中最富技巧性的方面之一。大部分的c++錯誤都是由於誤用指標或引用所導致的:

    • 對NULL解引用(嘗試對NUL指標使用->或*操作)
    • 二次釋放
    • 訪問非法記憶體區域
    • 混用記憶體分配器(如new對free,malloc對delete)
    • 陣列釋放不正確(使用delete而非delete []釋放陣列)
    • 記憶體洩漏
      當然這些指標問題可以使用託管指標之類的來避免(如共享指標,弱指標,作用域指標等)
  • 平臺獨立:設計精良的c++ API不應該在公共標頭檔案中出現平臺相關的#if/#ifdef語句。如果API給出了問題域的高層次的邏輯模型(API本身就應該如此),那麼API幾乎不會因平臺異。只有在編寫使用專用平臺資源的介面時,比如某個程式繪製一個視窗,併為本地作業系統傳遞正確的視窗控制代碼,這樣才合適。

5、 鬆耦合

耦合和內聚的概念如下:
- 耦合:軟體元件之間相互連線在一起的度量,即系統中每個元件對其他元件的依賴程度。
- 內聚:單個軟體元件內的各種方法相互關聯或聚合強度的度量。

優秀的軟體設計應該是低耦合(或鬆耦合)且高內聚的,即最小化不同元件之間功能的關聯性和連通性。評估元件之間的耦合度可採用下面幾種不同的度量方式:
- 尺度:與元件之間的連線數相關,包括類的數目,方法的數目,每個方法的引數的數目等。
- 可見度:指元件之間的連線顯著程度。
- 密切度:指元件之間連線的直接性。
- 靈活度:與改變元件之間連線的難易程度相關。

降低API內的類和方法的耦合度(API內耦合度)的技巧:

5.1 僅通過名字耦合

具體形式如下:

class MyObject; //只需要知道MyObject的名字
class MyObjectHolder
{
public:
    MyObjectHolder();
    void SetObject(MyObject* obj);
    MyObject* GetObject() const;
private:
    MyObject *mObj;
};

5.2 降低類耦合

Scott Meyers建議,如果情況允許,那麼優先宣告非成員、非友元的函式,而非成員函式。這樣做在促進封裝的同時還降低了這些函式和類的耦合度。例如

class MyObject
{
public:
    void PrintName() const; 
    std::string GetName() const;
    ...
private:
std:string mName;
};

依照Meyers的建議。應該優先使用一下表述:

class MyObject
{
public:
    std::string GetName() const;
    ...
private:
std:string mName;
};

void PrintName(MyObject& obj); 

後一種形式降低了耦合度,因為自由函式PrintName只能訪問MyObject的共有方法。

5.3 可以的冗餘

通常,優秀的軟體工程實踐的目標是去除冗餘,即保證每個重要的知識點或行為有且僅有一次實現。但有時,使用資料冗餘降低類之間的耦合是合理的。例如有一個文字聊天系統的API,該系統記錄使用者傳送的每條資訊。

class TextChatLog
{
public:
    bool AddMessage(const ChatUser& user, const std::string& msg);
    ...
private:
    struct ChatEvent
    {
        ChatUser mUser;
        std::string mMessage;
        size_t mTimestamp;
    };
    std::vector<ChatEvent> mChatEvents;
};

這裡顯然類TextChatLog和ChatUser是耦合的,如果最終TextChatLog類僅使用了類ChatUser中的使用者名稱(即呼叫ChatUser::getName()方法),那麼去除這兩個類的方法就是,在AddMessage方法中只傳入user name而非ChatUser類的物件。

class TextChatLog
{
public:
    bool AddMessage(const std:string& userName, const std::string& msg);
    ...
private:
    struct ChatEvent
    {
        std::string mUserName;
        std::string mMessage;
        size_t mTimestamp;
    };
    std::vector<ChatEvent> mChatEvents;
};

雖然這樣一來,類ChatUser和TextChatLog中都儲存了userName這個資料,但這卻消除了兩個類的耦合。

5.4 管理器類

就是通過一類(管理器類)來擁有並協調低層次的類。比如一個結構化的畫圖程式,他可以建立二維物件,選擇物件以及在畫布上移動物件,但這個程式支援多種輸入裝置,如滑鼠,手寫板,遊戲手柄等。一種簡單的設計就是要求選擇和移動操作了解每種輸入裝置(即,直接與每一種輸入裝置耦合)。另一種就是通過一個管理器類來協調對每個輸入裝置類的訪問。這樣選擇和移動類就只需要依賴管理器類,從而去除了與低層類的直接耦合。(具體例子,請參見書中 2.5.4 管理器類)

5.5 回撥、觀察者和通知

  • 回撥函式:在c/c++中,回撥是模組A中的一個函式指標,該指標被傳遞給模組B,這樣B就能在合適的時候呼叫A中的函式。模組B對函式A一無所知,並且對模組A不存在“包含(include)”或者“連結(link)”依賴。 回撥的這種特性使得低層程式碼能夠執行與其不能有依賴關係的高層程式碼。因此,在大型專案中,回撥是一種用於打破迴圈依賴的常用技術。

  • 觀察者:回撥給出的解決方法在純C中能夠正常工作,而正如前面提到的,如果不能使用類似boost::bind(類似java中的反射) 的輔助功能, 在面向物件的c++程式中使用回撥是非常複雜的。相比之下,更加面向物件的解決方法是使用觀察者的概念(即觀察者模式)

  • 通知:回撥和觀察者適用於特定的任務,他們的使用機制通常定義在執行實際回撥的物件中。一個替代的解決方案是,在系統中不連通的部分之間構建幾種傳送通知機制或事件。傳送者事先不需要知道接收者,這樣可以降低傳送者和接收者之間的耦合度。雖然通知機制有好幾種,但是最流行的是訊號和槽(由Qt庫引入的概念)。

6 穩定的、文件詳細且經過測試的API

  • 優秀的API設計應該是穩定的且具有前瞻性。
  • 優秀的API應該有很好的文件支援,以便使用者獲取API的功能、行為、最佳實踐以及錯誤條件的明確資訊。
  • 應該為API的實現編寫可擴充套件的自動化測試程式,確保新的變更不會破壞現有的用例。

相關推薦

API Design for C++》讀書筆記()API特徵

目錄 本章所講的內容都是在回答下面這個問題:優質的API應該具有哪些基本特徵? 1、問題域建模 編寫API的目的是解決特定的問題或是完成具體的任務。因此,API應該首先為問題提供一個清晰的解決方案,同時能對實際的問題域進行準確的建模。

API Design for C++》讀書筆記(一)

   背景:  常見的模組化開發、程式碼複用、元件化、動態連結庫(DLL)、 軟體框架、分散式計算以及面向服務的架構(SOA),都隱含了對髙超的API設計技能的需求。    目標:  健壯而優雅、穩定而耐用、抽象而隱藏,最主要的: 變更管理----應對變化、新需求、功能要求

CLR via c#讀書筆記字符、字符串和文本處理

頻繁 方法名 ros obj utf8 via title col point 1、在.NET Framework中,字符總是表示成16位unicode代碼值(關於unicode、utf8等可以到http://www.ruanyifeng.com/blog/2007/10/

深入理解JVM讀書筆記虛擬機器類載入機制

一、概述      虛擬機器把描述類的資料從class檔案載入到記憶體,並對資料進行校驗、轉換解析和初始化。最終形成可以被虛擬機器最直接使用的java型別的過程就是虛擬機器的類載入機制。      與那些在編譯時需要進行連線工作的語

深入理解JVM讀書筆記垃圾收集器與記憶體分配策略

一、判斷物件死亡的兩種常用演算法:                在堆裡面存放著java世界中幾乎所有的例項物件,垃圾收集器在堆進行回收前,第一件事情就是要確定哪些物件還存活著,哪些已經死去。 1、引

《深入淺出MySQL》 讀書筆記資料型別

1、int(11) 括號裡的 11  一般配合 zerofill使用,儲存時 仍然按照實際的精度儲存,整數型別的列可以設定 auto_increment ,需要同時設定為 not null primary key/unique key 2、float、double、decimal&nb

Effective C++讀書筆記之十複製物件時勿忘其每一個成分

class Date{...}; class Customer { public: ... private: std::string name; Date lastTransaction; }; class PriorityCustomer:public Customer { public: Pri

《構建之法》讀書筆記第一、、十六章

信息系統 做到 而是 需要 根據 style 成本 讀書 找到 這周看了鄒欣老師《構建之法》的1,2,16章,獲益匪淺。這本書寫得妙趣橫生,用阿超小飛幾個人的生活場景和幽默的比喻幫我理解著軟件工程的相關概念,讓我對軟件工程有了初步的了解:原來開發軟件並不是我們想的

讀書筆記25Temporal Hallucinating for Action Recognition with Few Still Images(CVPR2018)

openaccess.thecvf.com/content_cvpr_2018/papers/Wang_Temporal_Hallucinating_for_CVPR_2018_paper.pdf 摘要首先介紹背景,從靜態圖片中進行動作識別最近被深度學習方法促進,但是成功的

讀書筆記32PoTion: Pose MoTion Representation for Action Recognition(CVPR2018)

摘要首先介紹背景,很多一流的動作識別方法都依賴於two-stream的架構,一個處理appearance,另一個處理motion。接著介紹本文工作,本王呢認為將這兩個合起來考慮比較好,引入了一個新的representation,可以將semantic keypoints的

讀書筆記33SSNet: Scale Selection Network for Online 3D Action Prediction(CVPR2018)

摘要首先介紹問題,即action prediction(這裡括號寫了個early action recognition,看後面的介紹好像是和action recognition有區別的,區別在於並不是用已經獲得的整個video進行識別)的目的是用已觀測到的進行中的動作預測

資料結構(C語言)讀書筆記6棧的應用之括號匹配的C程式碼實現

括號匹配的演算法思想: 初始化一個空棧,掃描表示式,依次讀入字元,知道掃描完或者出現錯誤匹配。對於讀入的每個字元,分以下情況處理: (1)、如果是‘’(“”或“【”,將其壓入棧。 (2)、如果是“)”,則根據當前棧頂元素的值分情況考慮。若棧頂元素是“(”,則匹配成功,否則為

讀書筆記22Optical Flow Guided Feature: A Fast and Robust Motion Representation for Video Action Recogni

文章題目:Optical Flow Guided Feature: A Fast and Robust Motion Representation for Video Action Recognition(CVPR2018) 摘要部分:開頭一句話指出motion rep

讀書筆記31What have we learned from deep representations for action recognition?(CVPR2018)

摘要:首先是背景,深度模型在計算機視覺的每個領域都有部署,因此,理解這些深度模型得到的representation到底是怎麼工作的,以及這些representation到底抓去了什麼資訊就變得越來越重要。接著說本文的工作,本文通過視覺化two-stream模型在進行動作識

C/C++程式設計學習筆記C語言的函式中,如何使用指標交換兩個數的值,深入理解指標

 使用外部函式交換兩個變數的值,一個再簡單不過的事情,但是在C/C++中,正確實現該功能反應了你對指標和引用等重要知識的掌握程度。本文列舉了幾種常見的寫法,其中前三種是錯誤的,後兩種是正確的。第四種使

讀書筆記29A Closer Look at Spatiotemporal Convolutions for Action Recognition(CVPR2018)

本文的摘要開門見山介紹自己的工作,沒有背景介紹等鋪墊,這和本文的動作內容也有關,本文不是對比前人工作,針對某些不足提出新模型,而是討論了幾種不同形式的spatial temporal convolution模型,探討了其在動作識別中的效力,從實驗的角度證明了3D CNN的

Guava學習筆記()Google Guava (瓜娃)的API快速熟悉使用

1,大綱 讓我們來熟悉瓜娃,並體驗下它的一些API,分成如下幾個部分: IntroductionGuava Collection APIGuava Basic UtilitiesIO APICache API 2,為神馬選擇瓜娃? 瓜娃是java API蛋糕上的冰

Tolua使用筆記lua與C#的函式,變數互動方法

接著上一回的接著講: 案例三: 到這裡久終於到了大家最感興趣的地方了,熱更新的價值就在於用指令碼語言寫邏輯,這樣可以實現邏輯的頻繁改動而不用每次更新都重新下載。 而這個過程中必然涉及到C#對lua的一個邏輯呼叫,而這個例子實現的就是C#的函式的呼叫 核心程式碼如下:

C++ primer 讀書筆記

第三章字串 向量 陣列 string 初始化 初始化型別:拷貝初始化、直接初始化 使用=初始化即為,拷貝初始化。 char 陣列與string char 陣列如果沒有\0截止

C++Primer讀書筆記()

10.無符號數不會小於0   注意不能將帶符號型別和無符號型別混合使用。11.變數宣告與定義的關係   extern int i;//宣告i而非定義i(只宣告不定義用extern)   int j;//宣告並定義j   extern int i=1;//定義(賦值操作抵消了e