C语言面向对象编程(四):面向接口编程
? ? 在面試 Java 程序員時我經常問的一個問題是:接口和抽象類有什么區別。
? ? 很多編程書籍也經常說要面向接口編程,我的理解是,接口強制派生類必須實現基類(接口)定義的契約,而抽象類則允許實現繼承從而導致派生類可以不實現(重寫)基類(接口)定義的契約。通常這不是問題,但在有一些特定的情況,看起來不那么合適。
? ? 比如定義一個 Shape 基類,其中定義一個 draw() 方法,給一個什么都不做的默認實現(通常是空函數體),這實際沒有任何意義。
? ? 再比如基類改變某個方法的實現,而派生類采用實現繼承并沒有重寫這個方法,此時可能會導致一些奇怪的問題。以鳥為例,基類為 Bird ,我們可能會定義一個 fly() 方法,一個 walk() 方法,因為有的人認為鳥既可以走又可以飛。開始時我們在 walk() 的實現里作了假定,認為雙腳交叉前進才是 walk ,可是后來發現有些鳥是雙腳一齊蹦的,不會交叉前進。這個時候怎么辦?基類 Bird 的 walk() 方法是否要修改、如何修改?
? ? 在 C++ 中,沒有接口關鍵字 interface ,同時為了代碼復用,經常采用實現繼承。在 C 語言中,我們前面幾篇文章討論了封裝、隱藏、繼承、虛函數、多態等概念,雖然都可以實現,但使用起來總不如自帶這些特性的語言(如 C++ 、Java )等得心應手。一旦你采用我們前面描述的方法來進行面向對象編程,就會發現,在 C 語言中正確的維護類層次是一件非常繁瑣、容易出錯的事情,而且要比面向對象的語言多寫很多代碼(這很容易理解,面向對象語言自帶輪子,而 C 要自己造輪子,每實現一個類都要造一遍)。但有一點,當我們使用 C 語言作面向對象編程時,比 C++ 有明顯的優勢,那就是接口。
? ? 接口強制派生類實現,這點在 C 中很容易做到。而且我們在編程中,實際上多數時候也不需要那么多的繼承層次,一個接口類作為基類,一個實現類繼承接口類,這基本就夠了。在 C 語言中采用這種方式,可以不考慮析構函數、超過 3 層繼承的上下類型轉換、虛函數調用回溯、虛函數表裝配等等問題,我們所要做的,就是實現基類接口,通過基類指針,就只能操作繼承層次中最底層的那個類的對象;而基類接口,天生就是不能實例化的(其實是實例化了沒辦法使用,因為結構體的函數指針沒人給它賦值)。
? ? 一個示例如下:
[cpp] view plaincopy print?? ? 上面是頭文件,derived 結構體通過包含 base_interface 類型的成員 bi 來達到繼承的效果;而 base_interface 無法實例化,我們沒有提供相應的構造函數,也沒有提供與 func_1 , func_2 等函數指針對應的實現,即便有人 malloc 了一個 base_interface ,也無法使用。
? ? derived 類可以提供一個構造函數 new_derived ,同時在實現文件中提供 func_1 , func_2 ,func_3 的實現并將函數地址賦值給 bi 的成員,從而完成 derived 類的裝配,實現 base_interface 定義的契約。
? ? 示例實現如下:
[cpp] view plaincopy print?? ? 我們可以這么使用 base_interface 接口: [cpp] view plaincopy print?
? ? 上面的代碼中 do_something 函數完全按照接口編程,而 bi 可以實際指向任意一個實現了 base_interface 接口的類的實例,在一定程序上達到多態的效果,花費的代價相當小,卻可以讓我們的程序提高可擴展性,降低耦合。
? ? 這種簡單的方法也是我在自己的項目中使用的方法,效果不錯。
? ? 好啦,C 語言面向對象編程系列的基礎性介紹就告一段落,下面是前幾篇的鏈接,有興趣的可以回頭看看:
- C語言面向對象編程(一):封裝與繼承
- C語言面向對象編程(二):繼承詳解
- C語言面向對象編程(三):虛函數與多態
? ? 接下來我會提供幾個實作的例子,包括基本的數據結構,如單鏈表、樹,還有一個 http server 的例子。
總結
以上是生活随笔為你收集整理的C语言面向对象编程(四):面向接口编程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php多个参数绑定,php – 如何绑定
- 下一篇: C语言面向对象编程(五):单链表实现