[Rust] 程式設計教學:物件導向(Object-Oriented Programming)
文章推薦指數: 80 %
物件導向程式設計(OOP, object-oriented programming) 是一種程式設計的模式(paradigm)。
由於物件導向是近代軟體開發的主流方法,許多程式語言從語法機制可直接支援, ...
Togglenavigation開源教學精選項目C語言Golang資料結構網頁程式電子書籍現代C語言程式設計C語言應用程式設計多平台Objective-C程式設計跨平台CommonLisp程式設計社群媒體臉書粉絲團臉書社團推特GitHubGumroad本站資訊關於著作權免責聲明隱私權開源教學Rust程式設計物件導向(Object-OrientedProgramming)最後修改日期為AUG29,2017物件導向程式設計(OOP,object-orientedprogramming)是一種程式設計的模式(paradigm)。
由於物件導向是近代軟體開發的主流方法,許多程式語言從語法機制可直接支援,即使像是C這種視為非物件導向的語言,也可以用其他語法機制達到類似的效果。
本章會先介紹一般性的物件導向的概念,再介紹如何以Rust實作。
物件導向概論很多語言都直接在語法機制中支援物件導向,然而,每個語言支援的物件導向特性略有不同,像C++的物件系統相當完整,而Perl的原生物件系統則相對原始。
物件導向在理論上是和語言無關的程式設計模式,但在實務上卻受到語言特性的影響。
學習物件導向時,除了學習在某個特定語言下的實作方式外,更應該學習其抽象層次的思維,有時候,暫時放下實作細節,從更高的視角看物件及物件間訊息的流動,對於學習物件導向有相當的幫助。
物件導向是一種將程式碼以更高的層次組織起來的方法。
大部分的物件導向以類別(class)為基礎,透過類別可產生實際的物件(object)或實體(instance),類別和物件就像是餅乾模子和餅乾的關係,透過同一個模子可以產生很多片餅乾。
物件擁有屬性(field)和方法(method),屬性是其內在狀態,而方法是其外在行為。
透過物件,狀態和方法是連動的,比起傳統的程序式程式設計,更容易組織程式碼。
許多物件導向語言支援封裝(encapsulation),透過封裝,程式設計者可以決定物件的那些部分要對外公開,那些部分僅由內部使用,封裝不僅限於靜態的資料,決定物件應該對外公開的行為也是封裝。
當多個物件間互動時,封裝可使得程式碼容易維護,反之,過度暴露物件的內在屬性和細部行為會使得程式碼相互糾結,難以除錯。
物件間可以透過組合(composition)再利用程式碼。
物件的屬性不一定要是基本型別,也可以是其他物件。
組合是透過有…(has-a)關係建立物件間的關連。
例如,汽車物件有引擎物件,而引擎物件本身又有許多的狀態和行為。
繼承(inheritance)是另一個再利用程式碼的方式,透過繼承,子類別(childclass)可以再利用父類別(parentclass)的狀態和行為。
繼承是透過是…(is-a)關係建立物件間的關連。
例如,研究生物件是學生物件的特例。
然而,過度濫用繼承,容易使程式碼間高度相依,造成程式難以維護。
可參考組合勝過繼承(compositionoverinheritance)這個指導原則來設計自己的專案。
透過多型(polymorphism)使用物件,不需要在意物件的實作,只需依照其公開介面使用即可。
例如,我們想用汽車物件執行開車這項行為,不論使用Honda汽車物件或是Ford汽車物件,都可以執行開車這項行為,而不需在意不同汽車物件間的實作差異。
多型有許多種形式,如:特定多態(adhocpolymorphism):函數重載(functionaloverloading):同名而不同參數型別的方法(method)運算子重載(operatoroverloading):對不同型別的物件使用相同運算子(operator)泛型(generics):對不同型別使用相同實作子類型(Subtyping):不同子類別共享相同的公開介面,不同語言有不同的繼承機制以物件導向實作程式,需要從宏觀的角度來思考,不僅要設計單一物件的公開行為,還有物件間如何互動,以達到良好且易於維護的程式碼結構。
除了閱讀本書或其他程式設計的書籍以學習如何實作物件外,可閱讀關於物件導向分析及設計(object-orientedanalysisanddesign)或是設計模式(designpattern)的書籍,以增進對物件導向的了解。
類別在Rust,以struct或enum定義可實體化的類別,同時,也定義出一個新的型別。
見下例:然而,只有屬性而沒有方法的類別不太實用,下文會介紹方法。
另外,對於不可實體化的抽象類別,使用trait來達成;trait在物件導向及泛型中都相當重要,我們會於後續相關章節中展示其使用方式。
方法公開方法和私有方法方法是類別或物件可執行的動作,公開方法(publicmethod)可由物件外存取,而私有方法(privatemethod)則僅能由物件內存取。
以下為實例:在本程式中,我們用mod建立一個非公開的程式區塊,在其中的程式碼,除非用pub明確指定該部分的存取為公開的,否則,一律視為私有的,這是Rust在安全性上的設計。
實作方法時,用impl區塊包住要實作的方法,在impl中,若該方法和某個物件相關,第一個參數要加上&self或是&mutself,視可變性而定。
對沒有物件導向經驗的讀者來說,self是一個令人混淆的概念;基本上,self是一個特殊的變數,代稱物件,將方法加上self這個參數,代表該方法和某個物件相關。
以本程式來說,self.drive()代表這個物件呼叫了drive方法。
對應到主程式中,self代換為car這個實際的物件實體。
簡單的原則在於類別是物件的設計圖,而非物件。
在本程式中,對主程式來說,drive方法是不存在的,如果把car.run()改為car.drive()則會引發以下錯誤:由此可知,主程式的確無法存取drive方法。
Getters和Setters在先前Point的實作,我們直接存取物件的資料,在物件導向中,這不是好的方法。
比較好的方法,是將屬性私有化,而用公開方法存取。
我們現在建立Point類別,這個類別代表2D上的點。
首先,定義此類別及其屬性:實作建構子(constructor)的部分,建構子是一個特別的函式,用來初始化物件:new用來實體化Point類別。
在物件導向的術語中,稱此特殊方法為建構子;在Rust,new只是普通的方法,將new改為別的名稱也行,讀者可以將以上程式的new改為create或別的名稱看看,程式仍能正常運作。
不過,一般還是會把實體化物件的方法稱為new,這是約定俗成的用法。
在我們的建構子中,我們沒有直接將資料指派到物件屬性,而另外用setter來指派;雖然在本例中,我們的setter沒有特別的行為,但日後我們需要對資料進行篩選或轉換時,只要修改setter方法即可,其他部分的程式碼則不受影響。
在撰寫物件導向程式時,我們會儘量將修改的幅度降到最小。
接著,實作getters:最後,從外部程式使用此類別:由於本程式將物件屬性和setter皆設為私有,對外部程式來說,物件建立後不可修改。
在撰寫物件時,除了必要的方法外,儘量不要公開物件的其他部分,將公開行為變為私有的,可能會導致程式無法順利執行,應盡量避免。
帶有方法的enum使用enum也可以建立具有行為的類別。
在以下實例中,我們建立colormodel類別。
首先,宣告Color類別:在本例中,我們宣告了RGB(Red-Green-Blue)、CMYK(Cyan-Magenta-Yellow-Black)、HSL(Hue-Saturation-Lightness)三種colormodel,若有需要,也可以宣告其他的colormodel。
同一個Color物件,在同一時間內只會儲存其中一種colormodel,不同colormodel間有公式可以轉換,使用enum會比使用數個獨立的struct來得適合。
實作RGBmodel的建構子,由於RGBmodel是三個8位元非負整數來表示顏色的值,故用u8來儲存:實作CMYKmodel的建構子,由於CMYK的值為小於等於一百的百分比,我們額外建立一個私有類別方法來檢查值的正確性:實作HSLmodel的建構子,在這裡,我們用整數儲存角度,其值為[0,360)的區間。
:實作RGBcolormodel的getter,若物件內部儲存的colormodel是不同類型,則依公式來轉換:最後,從外部程式呼叫:在本例中,由HSL轉至RGB的公式較複雜,這裡不詳細講解,有興趣的讀者可自行查詢電腦圖學相關資料。
解構子若要實作Rust類別的解構子(destructor),實作Droptrait即可。
由於Rust會自動管理資源,純Rust實作的類別通常不需要實作解構子,但有時仍需要實作Droptrait,像是用到C語言函式配置記憶體,則需明確於解構子中釋放記憶體。
本書將於後續章節展示Droptrait的實作。
多型Rust使用trait來實作多型;透過trait,Rust程式可像動態語言般使用不同類別。
我們用一個實際的範例來說明。
首先,用trait設立共有的公開方法:接著,建立三個不同的汽車類別,這三個類別各自實作Drivetrait:最後,透過多型的機制由外部程式呼叫這三個類別:在本例中,Toyota、Honda和Ford三個類別實質上是各自獨立的,透過Drivetrait,達成多型的效果,從外部程式的角度來說,這三個物件視為同一個型別,擁有相同的行為。
由於trait無法直接實體化,必需藉助Box
組合勝於繼承Rust的struct和enum無法繼承,而trait可以繼承,而且trait支援多重繼承(multipleinheritance)的機制;trait可提供介面和實作,但本身無法實體化,反之,struct和enum可以實體化。
Rust用這樣的機制避開C++的菱型繼承(diamondinheritance)問題,類似Java的interface的味道。
組合就是將某個類別內嵌在另一個類別中,變成另一個類別的屬性,然後再透過多型提供相同的公開介面,外部程式會覺得好像類別有繼承一般。
假設我們要設計一個RPG遊戲,有兩個類別,分別是Creature和Character,而這兩者都可設定行動優先順序。
首先,決定公開界面,為了簡化問題,這裡僅實作優先權的getter/setter:接著,實作Creature類別:接著,實作Character類別,在這裡,我們透過組合的機制將Creature類別變成Character類別的一部分:`1最後,從外部程式呼叫這兩個類別:在本例中,Creature是Character的屬性,但從外部程式看來,無法區分兩者是透過繼承還是組合得到相同的行為。
在Rust和Go這兩個新興語言中,不約而同拿掉繼承的機制,改用組合搭配多型達到類似的效果,這樣的設計,是語言的進步還是語言的缺陷,就留給各位讀者自行思考。
前文提到struct無法繼承,而trait可以繼承。
以下展示一個繼承trait的實例。
首先,定義Color和Sharptrait,接著由Itemtrait繼承前兩個trait:實作Orange類別,該類別實作Colortrait:實作Ball類別,該類別實作Shapetrait:實作Basketball類別,該類別透過多型機制置入Color和Shape兩種類別:最後,透過外部程式呼叫Basketball類別:在本程式中,Item繼承了Color和Shape的方法,再加上自身的方法,共有三個方法。
在實作Basketball物件時,則需要同時實作三者的方法;在我們的實作中,color和shape這兩個屬性實際上是物件,由這些內部物件提供相關方法,但由外部程式看起來像是靜態的屬性,這裡利用到物件導向的封裝及組合,但外部程式不需擔心內部的實作,展現物件導向的優點。
在撰寫物件導向程式時,可將trait視為沒有內建狀態的抽象類別。
然而,trait類別本身不能實體化,要使用Box等容器來實體化。
這些容器使用到泛型的觀念,在本章,我們暫時不會深入泛型的概念,基本上,透過泛型,同一套程式碼可套用在不同型別上。
方法重載方法重載指的是相同名稱的方法,但參數不同。
Rust不支援方法重載,如果有需要的話,可以用泛型結合抽象類別來模擬,可見泛型章節中的範例。
運算子重載Rust的運算子重載利用了內建trait來完成。
每個Rust的運算子,都有相對應的trait,在實作新類別時,只要實作某個運算子對應的trait,就可以直接使用該運算子。
運算子重載並非物件導向必備的功能,像是Java和Go就沒有提供運算子重載的機制。
善用運算子重載,可減少使用者記憶公開方法的負擔,但若運算子重載遭到濫用,則會產生令人困惑的程式碼。
在本範例中,我們實作有理數(rationalnumber)及其運算,為了簡化範例,本例僅實作加法。
首先,宣告有理數型別,一併呼叫要實作的trait:實作其建構子,在內部,我們會求其最大公約數後將其約分:實作加法運算,在這裡,實作Addtrait,之後就可以直接用+運算子來計算:我們額外實作Displaytrait,方便我們之後可以直接從終端機印出有理數:最後,從外部程式呼叫此有理數物件。
由於我們實作了相關的trait,使用起來很類似基礎型別:如果我們繼續將這個範例發展下去,就可以實作有理數的運算系統,不過,這只是展示運算子重載的範例,我們就把實作有理數的任務留給有興趣的讀者。
在本例中,我們實作了兩個trait,Rust自動幫我們實作兩個trait,使得外部程式使用起來相當接近操作內建形別的過程,體現運算子重載的優點。
分享本文追蹤本站
延伸文章資訊
- 1[Rust] 程式設計教學:物件導向(Object-Oriented Programming)
物件導向程式設計(OOP, object-oriented programming) 是一種程式設計的模式(paradigm)。由於物件導向是近代軟體開發的主流方法,許多程式語言從語法機制可直接...
- 2Web開發學習筆記16 — OOP(Object Oriented Programming)
What Are JavaScript Constructor Functions? Day 10: ES6篇- Class(類別) · JavaScript | ES6 中最容易誤會的語法糖C...
- 3給自己的Python小筆記: 物件導向設計OOP教學. 哈囉 - Chwang
給自己的Python小筆記: 物件導向設計OOP教學. 哈囉,今天來跟大家介紹一下物件導向設計OOP,相信大家學程式就算沒用到,也都一定會聽到OOP這個概念,在學習程式的過程 ...
- 4Object Oriented Programming - Java備忘筆記 - GitBook
物件導向程式設計Object Oriented Programming (OOP) · 抽象化? Abstraction? · 好處? Benefits?
- 5物件導向程式設計(C#) - Microsoft Learn
在本教學課程中,您將擴充該應用程式,以利用繼承和多型來新增功能。 您也將將功能新增至 BankAccount 類別,利用您在上一個教學課程中學到的抽象概念和 ...