[物件導向Ep. 2] 三大特性 - CodiMD

文章推薦指數: 80 %
投票人數:10人

[物件導向Ep. 2] 三大特性 · 事前功課 · 三大特性 · 封裝Encapsulation · 繼承Inheritance 具備強烈隸屬關係時適用 · 多型Polymorphism 繼承的特殊應用,靜態型別語言適用(C++、 ...     1919views #[物件導向Ep.2]三大特性 ######tags:`MCL``oop` References ~-SpicyBoyd'sBlog:ClassDiagram類別圖[上篇](https://spicyboyd.blogspot.com/2018/07/umlclass-diagram-introduction.html)/[下篇](https://spicyboyd.blogspot.com/2018/07/umlclass-diagram-relationships.html) -[[visual-paradigm]UMLClassDiagramTutorial](https://www.visual-paradigm.com/guide/uml-unified-modeling-language/uml-class-diagram-tutorial/) -[[MCLDocs]yoyo.C++Lecture11:物件導向三大特性](https://docs.mcl.math.ncu.edu.tw/books/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/page/ep-11-%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91) 這篇文章將介紹物件導向會提供的三個核心特性。

##事前功課 完成以下使用UMLclassdiagram所描述的類別 ![](https://codimd.mcl.math.ncu.edu.tw/uploads/upload_034b8d392c927fe217910162a15047a9.png) 並使以下主函數可順利執行 -C++ ```cpp= usingnamespacestd; voidshowPublicMessage(Vehiclevehicle){ cout<封裝(Encapsulation)是指,一種將抽象性函式介面的實作細節部份包裝、隱藏起來的方法。

> >適當的封裝,可以將物件使用介面的程式實作部份隱藏起來,不讓使用者看到,同時確保使用者無法任意更改物件內部的重要資料,若想接觸資料只能通過公開接入方法(Publiclyaccessiblemethods)的方式(如:"getters"和"setters")。

它可以讓程式碼更容易理解與維護,也加強了程式碼的安全性。

> >[name=wikipedia] ![](https://codimd.mcl.math.ncu.edu.tw/uploads/upload_f2c249dc58bfdf1caabeebcee3c8b324.png) >封裝示意圖 >[name=yoyo.[C++Lecture11:物件導向三大特性](https://docs.mcl.math.ncu.edu.tw/books/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/page/ep-11-%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91)] ##繼承Inheritance具備強烈隸屬關係時適用 今天考慮說我們要建立一個新的類別叫做`Car`,他除了具備交通工具所需要的要素外,還需要有引擎發動與否的控制處理(如果引擎沒有發動,那他的移動速率就是零)。

用UML的觀點來看他會呈現以下形式: ![](https://codimd.mcl.math.ncu.edu.tw/uploads/upload_45260f7c2a85f50c6b18a50ab182467d.png) 其中你會看到`Car`和`Vehicle`在變數跟方法上有一定的相似程度(不同之處用顏色標記),而`moveForward`和`moveBackward`兩者因為在實作上需要多考慮`engine`是否為`true`,整體邏輯和`Vehicle`不一樣,所以在`Car`這邊需要**覆寫(override)**。

然而除了變數與方法上的差異外,在這兩者之間的一個關鍵要素,在於**汽車(`Car`)在概念意義上是交通工具(`Vehicle`)的一種,這是使用繼承與否最重要的判斷依據。

** 使用繼承後,從UML觀點來看他會變成以下樣貌 ![](https://codimd.mcl.math.ncu.edu.tw/uploads/upload_301f9b3becf2b0ae0a73f9c8dc462b86.png) 白色實心箭頭方向可以解讀成類別`Car`繼承類別`Vehicle`. 了解他的框架後,我們來看程式上如何實現: -C++ ```cpp= classCar:publicVehicle{ private: boolengine; public: Car(stringname):Vehicle(name,60,4){ this->engine=false; } //overridemoveForwardmethod voidmoveForward(inthours){ if(this->engine){ //callparentmethodwritteninVehicleclass Vehicle::moveForward(hours); } } //overridemoveBackmethod voidmoveBackward(inthours){ if(this->engine){ //callparentmethodwritteninVehicleclass Vehicle::moveBackward(hours); } } voidlaunchEngine(){ this->engine=true; } voidflameout(){ this->engine=false; } }; voidshowPublicMessage(Carvehicle){ cout<其中你會發現在`Car`類別裡面並沒有宣告如`name`、`getVelocity`等變數跟函數,而他們的作用會與`Vehicle`一致。

>然後這邊`showPublicMessage`偷偷使用了Overloading的特性:同樣的函數名稱,但有著不同的型態結構: >*`voidshowPublicMessage(Vehicle);` >*`voidshowPublicMessage(Car);` :::spoilerJava(待補) ::: :::spoilerPython3 ```python= classCar(Vehicle): def__init__(self,name): super(Car,self).__init__(name,60,4) self.engine=False defmoveForward(self,hours): ifself.engine: super().moveForward(hours) defmoveBackward(self,hours): ifself.engine: super().moveBackward(hours) deflaunchEngine(self): self.engine=True defflameout(self): self.engine=False ``` ::: :::spoilerJavaScriptES6^(待補) ::: 此範例程式碼執行結果如下: ``` [Toyota]vel:60,loc:0,#passengers:1 [Giant]vel:10,loc:100,#passengers:1 [Toyota]vel:60,loc:600,#passengers:1 ``` ###ProtectedC++、Java適用 根據上面的例子`moveForward`我們使用以下實作方法 -C++ ```cpp= voidmoveForward(inthours){ if(this->engine){ //callparentmethodwritteninVehicleclass Vehicle::moveForward(hours); } } ``` :::spoilerJava(待補) ::: 避免我們直接取用`location`和`velocity`這些在`Vehicle`的私有成員變數,那假若我們將方法改寫成 -C++ ```cpp= voidmoveForward(inthours){ if(this->engine){ this->location+=hours*this->velocity;//pass? } } ``` :::spoilerJava(待補) ::: 是否會編譯成功呢? 答案是**不行**的,這裡的私有是**僅限`Vehicle`自己使用,並不包括繼承他的類別。

**而假設今天程式設計師的需求是 1.外部(指實體化出來的物件)依然不可以直接取用, 2.但繼承他的類別中可以使用, 這時候你便需要在`Vehicle`對他們使用`protected`這個權限: -C++ ```cpp= classVehicle{ private: stringname; vectorpassengers; intcapacity; protected: intvelocity; intlocation; public: //... }; ``` :::spoilerJava(待補) ::: 在這樣的規範下,你才可以在`Car`當中直接取得這個變數 -C++ ```cpp= voidmoveForward(inthours){ if(this->engine){ this->location+=hours*this->velocity;//pass } } ``` :::spoilerJava(待補) ::: 回過頭來看UML,此時會使用`#`來表達`protected`的意思: ![](https://codimd.mcl.math.ncu.edu.tw/uploads/upload_3aa7077cac9baa10e44d49c11939ee09.png) >在這個案例當中,你依然可以透過`Vehicle::getVelocity()`和`Vehicle::getLocation()`這兩個公開方法取得這些值,不過當此變數完全沒有任何公開getter時,你可以透過這個方式去周全保護你不想開放的變數。

在這個章節的最後附上三個Accessmodifiers的權限規則: |AccessModifier\Identity|內部|繼承者|外部| |--------------------------|------------------|------------------|------------------| |public|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:| |protected|:heavy_check_mark:|:heavy_check_mark:|:x:| |private|:heavy_check_mark:|:x:|:x:| ##多型Polymorphism繼承的特殊應用,靜態型別語言適用(C++、Java) >對於動態型別語言則有一種概念叫做Ducktyping: >「當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子。

」 >[name=wikipedia:[Ducktyping](https://zh.wikipedia.org/zh-tw/%E9%B8%AD%E5%AD%90%E7%B1%BB%E5%9E%8B)] 首先我們定義另外一個也繼承`Vehicle`的類別,`Bicycle`。

![](https://codimd.mcl.math.ncu.edu.tw/uploads/upload_1e9e5782aea1c3e79d0002210b171f50.png) 從繼承的觀點出發,你可以理解成「每個交通工具,都有著共通的特性」,比如都會前進`moveForward`、後退`moveBackward`。

此時你有一種寫法是:你拿到一堆交通工具物件`Vehicle`(可能是汽車`Car`也可能是腳踏車`Bicycle`),呼叫著在`Vehicle`中存在的方法,並在各自物件中做不同的處理。

這個特性在當你遇到「面對不同的物件型態,施以相同的方法呼叫」時會發揮作用。

>Amajorityofcomputerprogramsneedtomodelcollectionsofvaluesinwhichthevalues >*areofthesametype,and >*mustbeprocessedinthesameway. > >[name=Fu-HauHsu.PrinciplesofProgrammingLanguages.Ch.6] 接下來以`moveForward``moveBackward`為例,我們討論在不同語言間要如何實現多型。

>####Dynamicpolymorphism與Staticpolymorphism >從程式碼到執行,基本上會經過解析、編譯、組譯、連結這些統稱編譯階段(Compile)後,到出現執行檔「雙擊」執行進入執行階段(Runtime)跑出結果。

> >在這裡動態與靜態的差別在於: >***動態多型**(Dynamicpolymorphism)在**執行階段(Runtime)**決定要使用(bind)哪個方法 >***靜態多型**(Staticpolymorphism)在**編譯階段(Compiletime)**決定要使用(bind)哪個方法 ###C++Polymorphism(Dynamicpolymorphism) C++實現多型(動態多型)的條件比較多一點,他必須滿足 -`Vehicle::moveForward`必須是一個抽象函數(abstractfunction) -此時`Vehicle`變成抽象類別(abstractclass),這種類別的特性是不能夠初始化(因為裡面的功能不完全) -因為不能是實體的物件,所以針對`Vehicle`的初始化只能是指標(儲存一個記憶體位址),在那個位址上的值是一個實際存在的物件(`Car`或是`Bicycle`的物件) 在討論程式碼如何實作前,我們先看在UMLDiagram上如何表達: ![](https://codimd.mcl.math.ncu.edu.tw/uploads/upload_c6590d32b01bfab65cb986840cb1297d.png) 其中 *斜體*Vehicle*代表該類別是抽象函數, *抽象函數在後面會加上`=0`這個標籤。

在C++,只要一個Class當中含有抽象函數,他就會自動變成抽象類別。

而抽象類別的寫法在C++需要在函數前面加上`virtual`關鍵字: ```cpp= classVehicle{ public: virtualvoidmoveForward(inthours)=0; virtualvoidmoveBackward(inthours)=0; }; ``` 在類別`Car`與`Bicycle`則可以選擇性加上`override`關鍵字(C++11以上適用) ```cpp= classCar{ public: voidmoveForward(inthours)override{ cout<location+=(engine?hours*this->velocity:0); } voidmoveBackward(inthours)override{ cout<location-=(engine?hours*this->velocity:0); } }; classBicycle{ public: voidmoveForward(inthours)override{ cout<location+=hours*this->velocity; } voidmoveBackward(inthours)override{ cout<location-=(engine?hours*this->velocity:0); } }; ``` >動態物件宣告方法為以下形式 > >```cpp >Car*car=newCar("Bob"); >car->moveForward(10); >``` > >這種寫法被稱為動態宣告,宣告出來後,`car`會是一個`Car`類型記憶體型態(指標),儲存一個實體`Car`物件的記憶體位址。

> >更多指標操作再另外拉章節出來講。

而在`main`函數中,我們以下列程式碼為範例,演示其特性: ```cpp= intmain(){ Vehicle*car=newCar("Toyota"); Vehicle*bicycle=newBicycle("Giant"); car->moveForward(10); bicycle->moveForward(10); return0; } ``` 該輸出為 ``` Car::moveforward Bicycle::moveforward ``` 我們再以陣列當作例子,可以凸顯出他更強大的功用: ```cpp= intmain(){ vectorvehicles; vehicles.push_back(newCar("Toyota")); vehicles.push_back(newBicycle("Giant")); for(inti=0;imoveForward(10); } return0; } ``` 此時你會發現當有多種型別的物件,且有未知數量個物件要呼叫同個名字的方法時,使用多型的概念我們就有能力去處理。

###[WIP]JavaPolymorphism



請為這篇文章評分?