<style>
.red {
color: red;
}
.blue{
color: blue;
}
.green{
color: green;
}
</style>
# 物件導向 (Object-Oriented Programming)
## 10-1 認識物件導向
- 物件導向(Object-Oriented)的優點是<span class='red'>物件可以在不同的應用程式中被重複使用</span>。
:::info
傳統的**程序性程式設計**(Procedural Programming)
- 是將資料和「用來處理資料」的函式分開定義,因此注重函式的設計
- 整個程式是由一連串的敘述(statement)所組成
- 逐步執行這些程式就可以得到結果
- EX: FORTRAN, ALGOL, BASIC, COBOL, Pascal, C, Ada等
:::
- 以下是物件導向中常見的名詞:
1. 物件(object)或實體(instance):
- 就像是生活中的所看到的物體
- 而物件有可能是由很多的子物件所組成
- 在python中,物件是資料與程式碼的組合
- 物件可以是應用程式,也可以是應用程式的一部分
2. 屬性(attribute)或成員變數(member variable):
- 用來<span class='red'>描述物件的特性或有什麼特性</span>
3. 方法(method)或成員函式(member function):
- 用來<span class='red'>定義物件的動作</span>
6. 類別(class):
- 是物件的分類,有點像是一個物件的樣板(tempelate)
- 相同類別的物件具有相同的屬性(attribute)和方法(method),不同的是屬性(attribute)的值
- 物件導向程式設計(OOP,Object Oriented Programming)有以下3個特點:
1. 封裝(encapsulation):
- 物件導向的設計是將資料和「用來處理資料」的函式放在一起,成為一個類別(class),這就稱為封裝(encapsulation)
- 因此注重物件與物件之間的操作
- 有了封裝的概念,類別內部的資料和函式就可以設定存取層級(access level)
- 例如可以設定為公開的屬性/方法或私有的屬性/方法
- 私有的屬性/方法可以限制只有類別內部的敘述可以存取
- 這可以將一些需要保護的資料和函式隱藏起來
2. 繼承(inheritance, §10-3):
- 指的是<span class='red'>從既有的類別定義出一個新的類別</span>
- 既有的類別我們稱為**父類別**(parent class),新的類別我們稱為**子類別**(child class, subclass)
- 子類別會繼承父類別「公開」的成員(member=attribute+method),也可以新增新的member或覆蓋(override)從父類別繼承來的method
- 繼承的優點是<span class='red'>提高軟體的重複使用性</span>
3. 多型(polymorphism, §10-4):
- 指的是<span class='red'>當不同的物件收到相同的訊息時,會以各自的方法來做處裡</span>
- 會看到override的概念
## 10-2 使用類別與物件
- <span class='red'>Python中所有的資料都是物件(object)</span>,舉例來說:數值是物件、字串也是物件
- 物件(object)是由類別(class)所定義出來的,整數的型別是由int這個類別定義出來的、浮點數的型別是由float這個類別定義出來的、字串的型別是由str這個類別定義出來的
- 物件是類別的實體(instance),我們可以根據相同的類別建立多個物件
- 建立物件的動作稱為實體化(instatiation)
- Python中的物件都有編號(id), 型別(type)與值(value):
1. id(x): 取得x物件的id編號
2. type(x): 取得x物件的型別
3. print(x): 印出x物件的值
### 10-2-1 定義類別 & 建立物件
```
>>> class CLASSNAME:
>>> statements
# statement是類別的主體,用來定義變數與函式
# 類別內的變數我們稱為屬性(attribute),類別內的函式我們稱為方法(method)
```
```
>>> import math
>>> class Circle:
>>> pi = 3.14
>>> radius = 2 # radius預設為2
>>> def getArea(self):
>>> return self.pi*pow(self.radius,2)
>>> c1 = Circle() #建立物件
>>> c1.radius = 7
>>> print("Area of c1 = ",c1.getArea())
Area of c1 = 153.86
>>> c2 = c1
>>> c2.radius = 3
>>> print("Area of c2 = ",c2.getArea())
Area of c2 = 28.26
```
### 10-2-2 __ init__()方法
- 在建立物建時,<span class='red'>會自動呼叫這個方法將物件初始化</span>
- 常見的初始化動作有:資料的初始值、開啟檔案、建立資料庫連接獲建立網路連線等
- __ init__()方法的第一個參數必須是self,參照剛才被建立的物件本身
- __ init__()在物件被建立時就會被執行,不需要再呼叫
```
>>> import math
>>> class Circle:
>>> pi = 3.14
>>> def __init__(self, r = 2):
>>> self.radius = r
>>> def getArea(self):
>>> return self.pi*pow(self.radius,2)
>>> c1 = Circle() #建立物件
>>> c1.radius = 7
>>> print("Area of c1 = ",c1.getArea())
Area of c1 = 153.86
>>> c2 = Circle(3)
>>> print("Area of c2 = ",c2.getArea())
Area of c2 = 28.26
>>> c3 = Circle()
>>> print("Area of c3 = ",c3.getArea())
Area of c3 = 12.56
```
### 10-2-3 匿名物件
- 一般來說,我們會先建立物件,然後將這個物件指派給一個變數,然後透過這個變數來存取物件
- 但Python允許我們<span class='red'>在沒有將物件指派給任何變數(c1, c2, c3)的情況下存取物件</span>
```
>>> import math
>>> class Circle:
>>> pi = 3.14
>>> def __init__(self, r = 2):
>>> self.radius = r
>>> def getArea(self):
>>> return self.pi*pow(self.radius,2)
>>> print("半徑為",Circle().radius, "的圓面積為", Circle().getArea())
半徑為 2 的圓面積為 12.56
>>> print("半徑為",Circle(10).radius, "的圓面積為", Circle(10).getArea())
半徑為 10 的圓面積為 314.0
```
### 10-2-4 私有成員(私有屬性與私有方法)
- 私有屬性限制只有內部的statement才能存取
- 若外部的statement想要存取必須透過類別提供的方法才能取得,換句話說,如果類別沒有提供取得的方法,則這個資料就不能被存取(讀/寫)
- <span class='red'>私有屬性和私有方法的名稱前面需要加上兩個底線__ </span>
- 我們可以藉由私有屬性與私有方法將一些需要保護的屬性和方法隱藏起來,達到資料隱藏(data hiding)的目的
```
>>> import math
>>> class Circle:
>>> pi = 3.14
>>> def __init__(self, r = 2):
>>> self.__radius = r #私有屬性
>>> def getRadius(self):
>>> return self.__radius
>>> def getArea(self):
>>> return self.pi*pow(self.__radius,2)
>>> c1 = Circle() #建立物件
>>> print("Area of c1 = ",c1.getArea())
>>> print("Radius of c1 = ",c1.getRadius())
Area of c1 = 12.56
Radius of c1 = 2
>>> c2 = Circle(5) #建立物件
>>> print("Area of c1 = ",c1.getArea())
>>> print("Radius of c1 = ",c1.getRadius())
Area of c1 = 78.5
Radius of c1 = 5
```
## 10-3 繼承
- 繼承(inheritance)是物件導向程式設計特點之一
- 所謂的繼承,指的是從已經定義好的類別(class)定義出一個新的類別
- 已經定義好的類別,可以稱為父類別(**parent class**)、基底類別(**base class**)或超類別(**super class**)
- 而新的類別,可以稱為子類別(**child class**、**subclass**)、衍生類別(**derived class**)或擴充類別(**extended class**)
- 子類別會繼承父類別的非私有成員(member=attribute+method),同時也可以新增新的成員或覆蓋(override)父類別的方法
- <span class='red'>繼承的優點是可以增加軟體的重複使用性</span>
- 另外Python內有強大的標準函示庫,還有第三方的函式庫(Library)。只要善用繼承的概念,就可以根據自己的需求從這些函式庫提供的類別定義出新的類別
- 原則上,<span class='red'>類別階層(class hierarchy)由上到下的定義應該是由廣義到狹義</span>
### 10-3-1 定義子類別
- 繼承只需要<span class='red'>在子類別的名稱後面加上小括號並加上要繼承的父類別名稱</span>
- 小括弧內是允許放入多個父類別的,這種繼承自多個父類別的方法我們稱作**多重繼承(multiple inheritance)**
```
>>> class A:
>>> __attr1 = "My attribute is __attr1"
>>> attr2 = "My attribute is attr2"
>>> def __method1(self):
>>> print("I am __method1")
>>> def method2(self):
>>> print("I am method2")
# B類別繼承父類別A
>>> class B(A):
>>> attr3 = "My attribute is attr3"
>>> def __method3(self):
>>> print("I am __method3")
# B類別內共有4個成員,包含非私有屬性attr2、非私有方法method2,還有新的屬性attr3和新的方法method3
```
#### 鍊狀繼承(chained inheritance)
- Python支援鍊狀繼承,舉例來說:類別B繼承父類別A,而類別C又繼承父類別B,這種情況我們就稱作鍊狀繼承(chained inheritance)
- 當然父類別是可以有很多的子類別的
```
>>> class A:
>>> attr1 = 4
>>> class B(A):
>>> attr2 = 3
>>> class C(B):
>>> attr3 = 2
>>> class D(C):
>>> attr4 = 1
# class A 內有1個成員,包含非私有屬性attr1
# class B 內有2個成員,包含非私有屬性attr1, attr2
# class C 內有3個成員,包含非私有屬性attr1, attr2, attr3
# class D 內有4個成員,包含非私有屬性attr1, attr2, attr3, attr4
>>> obj = D()
>>> print("attr1 = ", obj.attr1)
>>> print("attr2 = ", obj.attr2)
>>> print("attr3 = ", obj.attr3)
>>> print("attr4 = ", obj.attr4)
attr1 = 4
attr2 = 3
attr3 = 2
attr4 = 1
```
#### 多重繼承(multiple inheritance)
- 指的是<span class='red'>子類別可以繼承多個父類別</span>
```
class A:
attr1 = 4
class B():
attr2 = 3
class C():
attr3 = 2
class D(A,B,C):
attr4 = 1
# class A 內有1個成員,包含非私有屬性attr1
# class B 內有1個成員,包含非私有屬性attr2
# class C 內有1個成員,包含非私有屬性attr3
# class D 內有4個成員,包含非私有屬性attr1, attr2, attr3, attr4
obj = D()
print("attr1 = ", obj.attr1)
print("attr2 = ", obj.attr2)
print("attr3 = ", obj.attr3)
print("attr4 = ", obj.attr4)
attr1 = 4
attr2 = 3
attr3 = 2
attr4 = 1
```
### 10-3-2 覆蓋繼承自父類別的方法
- 覆蓋(override)指的是子類別將繼承自父類別的方法(method)重新定義,而且不會影響到父類別的方法
- 我們通常會<span class='red'>利用覆蓋(override)這個方法,來實作物件導向設計的多型(Polymorphism, §10-4)</span>
```
>>> class Employee:
>>> def __init__(self,name):
>>> self.__name = name
>>> def getName(self):
>>> return self.__name
>>> def getSalary(self, hours, payrate):
>>> return hours*payrate
>>> class SalesPerson(Employee):
>>> def getSalary(self, hours, payrate, bonus):
>>> return hours*payrate+bonus
>>> E1 = Employee("Vitas")
>>> E2 = SalesPerson("Pei")
>>> print("員工:",E1.getName(),"本月的薪水為 = ", E1.getSalary(130, 200))
>>> print("員工:",E2.getName(),"本月的薪水為 = ", E2.getSalary(130, 200, 10000))
員工: Vitas 本月的薪水為 = 26000
員工: Pei 本月的薪水為 = 36000
```
### 10-3-3 呼叫父類別內被覆蓋的方法
- 這是一種技巧,也就是在子類別中,可以呼叫父類別的方法來取代已經重複出現的statement
- 使用super( )可以找到父類別
```
>>> class Employee:
>>> def __init__(self,name):
>>> self.__name = name
>>> def getName(self):
>>> return self.__name
>>> def getSalary(self, hours, payrate):
>>> return hours*payrate
>>> class SalesPerson(Employee):
# 透過super()呼叫父類別的__init__()來設定銷售人員姓名
>>> def __init__(self, name, bonus):
>>> super().__init__(name)
>>> self.__bonus = bonus
>>> def getSalary(self, hours, payrate):
# 因為hours*payrate和父類別的方法相同,因此可以用super()來呼叫表示
>>> return super().getSalary(hours, payrate)+self.__bonus
>>> E1 = Employee("Vitas")
>>> E2 = SalesPerson("Pei", 10000)
>>> print("員工:",E1.getName(),"本月的薪水為 = ", E1.getSalary(130, 200))
>>> print("員工:",E2.getName(),"本月的薪水為 = ", E2.getSalary(130, 200))
>>> print(isinstance(E1, Employee))
True
# E2是Employee子類別的instance
>>> print(isinstance(E2, Employee))
True
>>> print(issubclass(SalesPerson, Employee))
True
員工: Vitas 本月的薪水為 = 26000
員工: Pei 本月的薪水為 = 36000
```
### 10-3-4 isintance()與issubclass()函式
- **isinstance(obj, class)**: 如果obj是class所指定的類別或其他子類別的物件(obj是class類別的物件嗎?),則回傳True,反之則False
- **issubclass(class, class2)**: 如果class1是class2所指定的類別的子類別(class1是class2類別的子類別嗎?),則回傳True,反之則False
```
>>> print(isinstance(100, int))
True
>>> print(isinstance(True, int))
True
```
## 10-4 多型
- 多型(Polymorphism)指的是<span class='red'>當不同的物件收到相同的訊息時,會以各自的方法來做處理</span>
```
>>> class Employee:
>>> def __init__(self,name):
>>> self.__name = name
>>> def getName(self):
>>> return self.__name
>>> def getSalary(self, hours, payrate):
>>> return hours*payrate
>>> class SalesPerson(Employee):
>>> def __init__(self, name, bonus):
>>> super().__init__(name)
>>> self.__bonus = bonus
>>> def getSalary(self, hours, payrate):
>>> print("You are SalesPerson")
>>> return super().getSalary(hours, payrate)+self.__bonus
>>> class Manager(Employee):
>>> def __init__(self, name, bonus):
>>> super().__init__(name)
>>> self.__bonus = bonus
>>> def getSalary(self, hours, payrate):
>>> print("You are Manager")
>>> return super().getSalary(hours, payrate)+self.__bonus
>>> E1 = Manager("Vitas", 20000)
>>> E2 = SalesPerson("Pei", 10000)
>>> print("員工:",E1.getName(),"本月的薪水為 = ", E1.getSalary(130, 200))
You are Manager!
員工: Vitas 本月的薪水為 = 46000
>>> print("員工:",E2.getName(),"本月的薪水為 = ", E2.getSalary(130, 200))
You are SalesPerson!
員工: Pei 本月的薪水為 = 36000
```