第十二篇:為使用者設計良好的介面

穆晨發表於2017-01-27

前言

       作為一名優秀的程式設計師,必須保證自己的程式碼能提供正確的,完善的介面,如此方能和同事,甲方更好的溝通合作,也讓自己的程式碼更加地容易維護。

       本文將介紹一些設計優秀介面的思路。

思路一:匯入新的型別

       下面還是先看這個例子,我定義了一個儲存日期的 Date 類:

1 class Date
2 {
3 public:
4     Date(int month, int day, int year);
5     // ......
6 };

       可用以下方法定義一個 Date 物件:

1 Date d(30, 3, 1995);

       可有些使用者會犯很蠢的錯誤,比如:

1 Date d(30, 3, 1995);

       顯然,他使用者將介面的引數輸錯位了。然而,優秀的介面應當能夠友好反饋錯誤資訊給使用者,這種情況下,最好的策略就是定義新的型別,請參考下面這個 Date 類的設計:

 1 class Day
 2 {
 3 public:
 4     explicit Day(int d)
 5         :val(d) {}
 6     // ......
 7 private:
 8     int val;
 9 };
10 
11 class Month
12 {
13 public:
14     static Month Jan() {
15         return Month(1);
16     }
17     static Month Feb() {
18         return Month(2);
19     }
20     // ......
21 
22 private:
23     explicit Month(int m) {
24         val = m;
25     }
26     // ......
27 
28     int val;
29     // ......
30 };
31 
32 class Year
33 {
34 public:
35     explicit Year(int y)
36         :val(y) {}
37 private:
38     int val;
39 };

       而定義一個 Date 物件,可採用如下方式:

1 Date d(Month::Feb(), Day(30), Year(1995));

       在日,月,年各個類中,還可以實現更高階的封裝。

思路二:引導使用者進行正確編碼

       這裡繼續上一篇文章中提到的智慧指標的一個例子,這裡要說明的是,當時給出的那個工廠函式:

1 class Investment
2 {
3     // ......
4 };
5 
6 Investment * createInvestment();

       並不是很好的一種設計。

       為啥?因為使用者可能忘了使用智慧指標把 Investment * 接過去。而使用下面的工廠函式介面設計可以有效的避免這個問題:

1 std::tr1::shared_ptr<Investment> createInvestment();

       這樣就讓使用者你不用智慧指標都不行了,哈哈。

       甚至你還可以更過分,指定智慧指標在資源被指數為0的時候要呼叫的解構函式:

std::tr1::shared_ptr<Investment> createInvestment()
{
    // 指定智慧指標型別及刪除器
    std::tr1::shared_ptr<Investment>retVal (static_cast<Investment *>(0), getRidOfInvestment);
    
    // retVal = ...
    // 令 retVal 指向正確的物件

    return retVal;
}

       上段程式碼中的getRidOfInvestment是你自己指定的刪除器。

思路三:限制型別什麼事情可以做什麼事情不能做

       使用 const,explicit等限制性關鍵字,遮蔽無用的拷貝建構函式等可以做到這點。

       這些在以前的文章中均有講解。

思路四:使你的類儘量表現得像內建型別

       要做到這點可不簡單,你需要以"當初語言設計者設計語言內建型別時"那般謹慎的思考class的設計,對設計出的class,我們需要問自己以下幾個問題:

       1. 新的物件資源在何時建立? 何時銷燬?

              這部分同樣涉及到建構函式,解構函式的編寫。

       2. 物件的初始化和賦值應該有什麼樣的差別?

              這部分涉及到建構函式,拷貝建構函式,賦值運算子的編寫。不要混淆這兩個概念。

       3. 如果物件發生了值傳遞,意味著什麼?

              你得仔細考慮這期間發生的資源相關的一些問題。

       4. 哪些物件是合法範疇?

              物件的成員是不是合法,這點很重要。它影響到了你諸多成員函式的錯誤檢查工作,也影響到了丟擲的異常。

       5. 新的類需不需要配合某個繼承圖系?

              如果這個類的子類要實現多型,那麼成員函式就得宣告為虛擬函式;如果這個類繼承自其它類,那麼當你自定義拷貝建構函式或者過載賦值運算子的時候,也得對父類部分做出處理。

       6. 什麼樣的操作符和函式對這個新的型別來說是合理的?

              需要考慮這個型別應該對哪些運算子過載,還有哪些函式被當做成員函式,哪些用非成員函式實現。

              具體的選取規則,以後會有篇文章專門講。

       7. 什麼樣的標準函式應當駁回?

              將它宣告為 private

       8. 新的型別成員將被哪些物件取用?

              這個涉及到變數private,protected,以及友元相關機制。

       9. 新的型別是否應當滿足一般化的要求?

              如果你要定義的是一個類家族,那麼你需要的不止是一個類,而是一個類别範本

小結

       1. 類的設計不要貪快。要儘量滿足,實現這些規則,貪快會導致開發後期事倍功半。

       2. 本文應當在實際專案中進行類設計的時候邊設計邊看,如此,方能有顯著的提高。

相關文章