用C語言寫物件導向程式(OOP)

Heron Yang
Heron’s Blog 海龍的部落格
6 min readAug 3, 2019

--

封裝(Encapsulation)、繼承(Inheritance)、多形(Polymorphism)這三者大多數的人認為是高等的物件導向程式語言如C++、Java等等才提供的功能;作者Uncle Bob在Clean Architecture書中認為這是至少五十年前C語言程式設計師就常常做的事情了。

我記得大學時候也聽過學長說過這麼一件事情,當時聽不太懂,而今我覺得是一件非常有趣的事情。

什麼是物件導向程式?

作者Uncle Bob在Clean Architecture書中這麼問:什麼是物件導向程式?

  • 有人說是「資料與函式的組合」,但這不對,即使物件導向能讓你寫o.f()這樣的語句,但是跟f(o)有什麼差別呢?
  • 有人說是「一種表示真實世界的模型」,這也許正確但卻很模糊,所以依然沒有辦法明確定義什麼是物件導向。
  • 又有人說是提供「封裝(Encapsulation)」、「繼承(Inheritance)」、「多形(Polymorphism)」這三種能力的程式語言及是物件導向,作者Bob則認為這用C語言也可以。

封裝 Encapsulation

封裝表示一份程式只提供使用介面,但把實現細節(如不需要給Caller看到的資料或函式)隱藏起來。也就是說,我們把程式拆成兩部分,一個是給Caller看的介面,另一份是Implementation。

Bob用C語言寫起來是這樣的:

point.h
point.c

另外把C++語言的作法也寫出來比較一番:

point.h
point.cc

可以看到C或C++都可以清楚的把Header(給Caller的介面)和Implementation(實作細節)分開,Bob甚至認為C語言在此表現更好,因為在C++語言中Private的變數仍出現在Header裡面,C語言中可以更理想的把他們藏進.c檔案。

繼承 Inheritance

C語言也是可以實作繼承的。

labeledPoint.h
labeledPoint.c
main.cc

這個例子中,我們看到LabeledPoint繼承了Point,然後可以套用上面原本給Point的方式運算兩個LabeledPoint的getDistance。最特別的地方是在傳入getDistance時我們把LabeledPoint struct轉換成Point,且在getDistance裡面可以正常取出x與y,原因是因為struct裡面的元素在Memory裡是依序擺放的,所以從上而下的x與y兩者在Point struct和LabeledPoint struction 是可以用一樣方式取得的。

多形 Polymorphism

多形表示在同一個介面上、根據不同的資料型態會有不同的方式處理。

假設有一隻小程式想進行複製的動作:

這時getchar()會從STDIN讀字元進來,putchar()寫進STDOUT。而這兩個函式是支援多形的,他們會根據STDIN/STDOUT的型態不同而有不同做法。

在Unix系統中要求輸出入裝置要提供五種基本的函式:

而一個輸出入裝置的Driver則會去實現這五個函式,然後裝進FILE struct中:

於是如果STDIN被定義成FILE*且指向上面的console資料結構,getchar()實際上就能長這樣:

如此一來我們就能讓getchar()內部的實現依據裝上不同的輸出入裝置而不同,實現了多形的效果。

什麼!?

所以到底什麼是物件導向?

我們用了各種方式去解釋什麼是物件導向,然而因為C語言也都能做到一樣的事情,所以以上的理由都一一被推翻了。於是,到底到底什麼是物件導向呢?

在物件導向之前,C語言中的Calling Tree(誰呼叫誰而畫成的一顆關係樹),最上方是main函式,往下呼叫中階層、再往下呼叫更下階層的東西,這樣的Flow of Control(誰呼叫誰的關係)也創造了絕對的Source Code Dependency。作者Bob說 “Every caller was forced to mention the name of the module that contained the called.”

然而在物件導向中,Caller與Calle之間多了Callee的Interface,所以兩者之間不再有絕對的Source Code Dependency,稱這個現象為Dependency Inversion。也就是如此,軟體設計師擁有絕對的控制權去決定Code Dependency,不受限於是誰Call誰。

舉個例子,假設 “Business Rules” 中會用到 “Database” 和 “UI”,在使用物件導向設計中,我們可以把 “Database” 與 “UI” 插入 “Business Rules” 中但卻又不創造 “Business Rules” 對於 “Database” 和 “UI” 的Source Code Dependency,甚至可以讓這三個部分分開部署(Independent Deployability)。作者認為這點才是物件導向帶來的效益。

結語

封裝(Encapsulation)、繼承(Inheritance)、多形(Polymorphism)三者其實不用物件導向也可以做到,而物件導向到底帶來什麼,讓我Quote作者Bob的話吧:

OO is the ability, through the use of polymorphism, to gain absolute control over every source code dependency in the system. It allows the architect to create a plugin architecture, in which modules that contain high-level policies are independent modules that contain low-level details. The low-level details are relegated to plugin modules that can be deployed and developed independently from the modules that contain high-level policies.

What More?

Follow Heron’s Blog on Medium or Facebook.

--

--