[物件導向Ep. 2] 三大特性 - CodiMD
文章推薦指數: 80 %
[物件導向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)
>
用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;
vector
在這個章節的最後附上三個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(){
vector
###[WIP]JavaPolymorphism
延伸文章資訊
- 1[Java] 2. OOP三大特性:封裝、繼承、多型 - YaYi
兩者的不同:程式導向很直接,一步步執行,一路到底;而物件導向是先抽象,把事物分類成不同的類,劃分每個類的所能執行的動作,然後按邏輯執行時呼叫每個 ...
- 2面試官:物件導向的三大特性和五大原則是什麼? | IT人
物件導向的三大特性封裝,繼承,多型什麼是封裝? 把客觀的事物封裝成抽象的類,並且類可以把自己的資料和方法只讓可信的類或者物件操作, ...
- 3OOP 物件導向的四個特性 - Corey Chen's Blog
物件導向四個特性:. 1. 抽象(Abstraction); 2. 封裝(Encapsulation); 3. 繼承(Inheritance); 4. 多型(Polymorphism).
- 408. 物件導向的特性—封裝、繼承、多型、抽象(撰寫中)
提到物件導向設計(Object-Oriented Programming, OOP),一定會提到它的三大特性,分別是封裝、繼承、多型。此外,還有一個東西,筆者覺得很重要,在實務經常使用,所以 ...
- 5物件導向(Object Oriented Programming)概念| by Po-Ching Liu
即是將物件內部的資料隱藏起來,只能透過物件本身所提供的介面(interface)取得物件內部屬性或者方法,物件內部的細節資料或者邏輯則隱藏起來,其他物件即無法瞭解此物件的 ...