Julia 的物件導向
今天特別要來談談Julia這個語言的物件導向模型。
我已經聽到有些人跟我說「Julia才沒有物件導向」或是「我不承認這是物件導向」等等這類的話。
不過我得要跟大家說,物件導向不是只有大家看到的C++或是Java那個樣子的物件導向模型。
介紹 Julia 的物件導向模型
如果你的背景是C++, Java, Python, Ruby等等語言的話,那你應該非常熟悉物件導向的幾個要素:
封裝
繼承
多型
談到封裝,Julia幾乎沒有封裝的概念。
是的,Julia不封裝!
Julia宣告的是型別,不是類別;型別他只宣告了有哪些欄位,他也不會把方法宣告在型別裡頭。
所以Julia的型別感覺上會比較像是C的struct
,只存資料或狀態,不帶任何的方法。
那方法怎麼辦?
方法會由 Julia 的多重分派(multiple dispatch)引入。
也就是說,我只要去設計任何函式,這個函式他所用到的參數型別,那這個函式就是這些參數 的方法。
Julia會視函式呼叫的參數型別組合,來決定到底要執行哪一種方法。
看完之後應該會很傻眼,既然沒有封裝,然後又不帶方法,那這樣要怎麼實現物件導向呢?
Julia 的型別系統
概念上來說,Julia會藉由兩種型別來建立型別階層(type hierarchy):抽象型別跟複合型別
1 2 3 4 5 6 7 abstract type Number end abstract type Complex <: Number end abstract type Real <: Number end struct Integer <: Real x end
以上宣告了 3 個抽象型別以及一個複合型別。
抽象型別只有一行宣告,也就是說明這代表什麼樣的概念,他不具有欄位。
<:
代表的是 is subtype of ,abstract type Complex <: Number end
代表的是Complex
是Number
的子型別。
複合型別就像Integer
,他有一個field x
,他是Real
的子型別。
子型別可以是抽象型別或是複合型別,但相反的是,父型別只能是抽象型別。
以上我們描繪了一個型別階層,位於最頂端的是Number
,所有人都是Number
的子型別。
而複合型別只能位於這棵型別階層的末端的位置。
在Julia中,所有的型別都是Any
的子型別,相反,所有型別的子型別則是Union{}
。
這樣的階層完全不俱備傳統物件導向的繼承概念,雖然觀念上也滿足is-a 的關係,但是他並沒有從父型別上繼承任何東西,也沒有東西可以被繼承。
你可能會問:那這樣的型別階層有什麼用呢?
這就要配合多重分派來用拉~~
Julia 的多重分派
大家比較熟悉的應該是single dispatch,也就是區別foo.method()
與bar.method()
的不同。
傳統的物件導向當他呼叫foo.method()
與bar.method()
這兩者時,同樣都是呼叫method()
這個函式,可是到底要呼叫誰的實作呢?
Single dispatch會告訴你:去找擁有這個函式的人,也就是.
前面的那個傢伙。
但是Julia的設計理念是:函式並不屬於任何人,也就是不會有人擁有 函式。
所以Julia裡的函式會是method(foo)
跟method(bar)
這樣的形式。
Julia設計上很聰明,他會告訴函式去執行擁有跟參數型別相同排列組合的那個實作。
這也就是多重分派了,他不依賴單一的型別去判斷,而是所有的參數型別的組合!
所以當你宣告了一個像這樣的函式:
1 2 3 function foo(a, b, c) .... end
他會接受任意的3個參數 ,當沒宣告參數型別時就自動是Any
。
那以下這個狀況:
1 2 3 function foo(a::Int64, b) .... end
就只接受第一個參數型別是Int64
,而輸入兩個參數 的情況了。
OOP in Julia
以這樣的type system以及多重分派作為建構物件導向的核心機制。
我們不能在以傳統的思維去想物件導向。
我們需要思考的是物件導向的核心是什麼?
物件導向的引入主要幫助我們去塑造一個物件概念,並且幫助我們減少重複的程式碼,提高程式碼的再利用(reuse)。
我們訴求的是高內聚,低耦合的程式碼。
在Julia中,複合型別提供了一個類似物件、結構的角色,由於沒有繼承關係,也不會有過高的內聚。
繼承所需要的元素可以由合成(composition)來達成,但相對犧牲了部份的欄位再利用的可能性。
方法的部份還原成了最初始的函式的樣貌,而函式本身是更加自由的。
藉由多重分派可以更加細緻地 定義函式的行為,可以在不同的參數型別上提供多樣的語意。
例如,*(Int64, Int64)
的含意是數字的乘法,*(String, String)
則是字串的串接,同樣的運算子或是函數名稱,可以提供不同context下,不同的語意,這不是很像人類的語言習慣嗎?
在Julia的精神中,我們關注的是context跟行為,去定義不同context下的函式以及語意(行為)是重要的。
這方面Julia提供了絕佳的多型(polymorphism),但完全不支援繼承與封裝,對資訊的存取權更是無法控制。
但他更強調的是readability!如果設計者考慮到context,那讀程式碼的人就更能進入這個context去思考。
在函式的使用上更是毋庸置疑的靈活而鬆耦合,所以在程式碼的撰寫上,可以自然的達成高內聚,低耦合 ,更可以提升readability,最後的再利用就端看設計者最佳化的功力拉!
Julia是一個達成物件導向精神的語言!
State-centered and behavior-centered OOP
The state-centered OOP
傳統大家認知的物件導向,大多都由一個概念 或是實體 所構成。
大家在做程式設計的時候,大家所關注的大多是一個變數的值是如何變化 或是一個物件的狀態如何變化 。
所以很自然的會從一個實體或是概念著手,進而去設計他應該有怎樣的properties或是做什麼事情。
會慢慢凝聚出一個概念,並且把這個概念化成一個個的class,並把這個class描繪清楚。
class描繪仔細了以後,就去關注class跟class之間的互動關係,進而完成整個演算法或是架構。
The behavior-centered OOP
在Julia的物件導向則是敘述關係 或是行為 。
Julia會以函式的角度出發,Julia中的物件導向指的是design a set of methods 這回事。
Julia會使你關注在參數之間的關係,他們應該如何一起完成工作?參數如何被dispatch?函式跟函式之間會不會衝突?
型別應該會有什麼樣的行為?*
用在數字上跟用在字串上的語意有沒有一樣?
方法其實是在圈定一群型別,他們會一起作用,就是這麼簡單。
所以Julia的物件導向觀點都會圍繞在設計方法上面,而這樣的觀點又可以讓Julia很自然的融入functional programming當中。
這讓Julia可以花比較少力氣在斤斤計較欄位的設計上,也花比較少力氣在思考繼承關係上。
相對花比較多力氣在這個方法被宣告、進入整個語言系統當中的運作情形,也就是對整個系統的影響上。
留言與分享