BACK
Featured image of post 物件導向的 Q&A - 以 Python 舉例

物件導向的 Q&A - 以 Python 舉例

聊聊幾個常見的物件導向問題與解答,並以 Python 來做舉例。

參考網站
參考網站


物件導向

請問物件導向程式語言有哪些特點,能簡單說明給我聽嗎?

物件導向程式語言 (Object-Oriented Programming,OOP) 的特點有:

  1. 物件導向:OOP 程式語言是以物件 (Object) 為基礎建構程式,物件具有狀態 (state) 和行為 (behavior)。
  2. 繼承 (Inheritance):OOP 程式語言允許一個類別 (class) 從另一個類別繼承屬性和行為。
  3. 多型 (Polymorphism):OOP 程式語言允許物件在不同的情況下具有不同的行為。
  4. 封裝 (Encapsulation):OOP 程式語言允許將物件的狀態和行為封裝在一起,保護物件內部狀態不被外界直接存取。
  5. 抽象 (Abstraction) ,OOP 程式語言將物件的實作細節與使用者分開,使用者只需要知道物件的公開介面而無需知道其實作。

請問物件具有狀態 (state) 和行為 (behavior)。這句話是什麼意思?能用 python 舉例嗎?

物件具有狀態 (state) 和行為 (behavior) 意思是物件具有兩種不同的特性,狀態代表物件的屬性,行為代表物件的方法。

舉例來說,若我們有一個類別叫做 Car,那麼這個類別可能有一些狀態屬性如 “color”、“speed”、“brand”,而行為可能有 “drive”、“brake”、“turn” 等方法。

用 python 程式碼來實現這個類別可能長得像下面這樣:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Car:
  def __init__(self, color, speed, brand):
    self.color = color
    self.speed = speed
    self.brand = brand
      
  def drive(self):
    self.speed += 10
      
  def brake(self):
    self.speed -= 10
      
  def turn(self, direction):
    print(f"The car is turning {direction}")
        
my_car = Car("red", 0, "Tesla")
my_car.drive()
my_car.turn("left")
print(my_car.speed) # 10

上面這個例子中,類別 Car 有三個狀態屬性 color、speed、brand 以及三個行為方法 drive()、brake()、turn()。

透過創建一個實體物件 my_car = Car("red", 0, "Tesla") 來使用這些方法及屬性。

請問你能用上面的 Car class 寫一個繼承的範例給我看嗎?

當然可以,我們可以創建一個新的類別來繼承 Car 類別,讓新類別繼承 Car 類別的屬性和方法。

例如我們可以創建一個新的類別 ElectricCar 繼承 Car 類別,並且在 ElectricCar 類別中加入新的屬性和方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class ElectricCar(Car):
  def __init__(self, color, speed, brand, battery_size):
    super().__init__(color, speed, brand)
    self.battery_size = battery_size
      
  def charge(self):
    print("The electric car is charging...")
      
  def drive(self):
    if self.battery_size > 0:
      self.speed += 20
      self.battery_size -= 10
    else:
      print("The battery is empty, please charge the car.")
            
my_electric_car = ElectricCar("blue", 0, "Tesla", 100)
my_electric_car.drive()
my_electric_car.drive()
my_electric_car.drive()
print(my_electric_car.speed) # 60
print(my_electric_car.battery_size) # 70

在這個例子中,ElectricCar 類別繼承了 Car 類別的所有屬性和方法,並且定義了新的屬性 battery_size 和方法 charge()

透過使用 super() 函式可以繼承 Car 類別的 init 方法。

使用 my_electric_car = ElectricCar("blue", 0, "Tesla", 100) 來創建實體物件,可以使用 ElectricCar 類別的新屬性及方法。

請問你能用上面的程式碼繼續舉例多型的用法給我看嗎?

當然可以,多型 (Polymorphism) 是指物件在不同的情況下具有不同的行續。 一般來說我們可以使用繼承的類別來實現多型。

以上面程式碼為例,我們可以用 ElectricCar 類別繼承 Car 類別,並重新實現 drive 方法來實現多型的用法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class ElectricCar(Car):
  def __init__(self, color, speed, brand, battery_size):
    super().__init__(color, speed, brand)
    self.battery_size = battery_size
      
  def charge(self):
    print("The electric car is charging...")
      
  def drive(self):
    if self.battery_size > 0:
      self.speed += 20
      self.battery_size -= 10
    else:
      print("The battery is empty, please charge the car.")
            
class GasCar(Car):
  def __init__(self, color, speed, brand, gas_level):
    super().__init__(color, speed, brand)
    self.gas_level = gas_level
      
  def fill_gas(self):
    self.gas_level = 100
  
  def drive(self):
    if self.gas_level > 0:
      self.speed += 15
      self.gas_level -= 10
    else:
      print("The gas is empty, please fill the gas.")

my_electric_car = ElectricCar("blue", 0, "Tesla", 100)
my_gas_car = GasCar("black", 0, "Ford", 60)

def drive_car(car):
  car.drive()

drive_car(my_electric_car)
drive_car(my_gas_car)

print(my_electric_car.speed) # 20
print(my_gas_car.speed) # 15

在這個例子中,我們在 ElectricCar 和 GasCar 類別中都重新實現了 drive 方法,並且在 drive_car 函數中只接受 Car 的類別,可以看到在 drive_car 函數中傳入不同類別的實體物件會執行不同的 drive 行為

這就是多型的用法,可以讓同一個函數或方法支援多種類別的物件,而不需要為每一種類別都寫一個不同的函數或方法。這樣可以增加程式碼的彈性和可讀性。

我們可以在設計類別時統一實現某些方法,並讓類別可以自行選擇如何實現該方法,這樣就可以讓程式碼更具彈性和靈活性。

請問你能用上面的程式碼繼續舉例封裝的用法給我看嗎?

當然可以,封裝 (Encapsulation) 是指將物件的狀態和行為封裝在一起,保護物件內部狀態不被外界直接存取。

在 Python 中,我們可以使用 ___ 來表示私有變數和方法,這樣的變數和方法只能在類別內部存取

例如,我們可以把 ElectricCar 類別中的 battery_size 屬性改成私有變數:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class ElectricCar(Car):
  def __init__(self, color, speed, brand, battery_size):
    super().__init__(color, speed, brand)
    self.__battery_size = battery_size
      
  def charge(self):
    print("The electric car is charging...")
      
  def drive(self):
    if self.__battery_size > 0:
      self.speed += 20
      self.__battery_size -= 10
    else:
      print("The battery is empty, please charge the car.")
          
  def get_battery_size(self):
    return self.__battery_size
    
my_electric_car = ElectricCar("blue", 0, "Tesla", 100)
print(my_electric_car.get_battery_size()) #100

在這個例子中,ElectricCar 類別的 battery_size 屬性被定義為私有變數,只能在類別內部存取。如果外部直接存取該變數的話會產生錯誤。

而我們可以使用私有變數所對應的公有方法 get_battery_size() 來存取私有變數。這樣可以保護類別內部狀態不被外部直接存取,也可以通過公有方法給外部提供適當的存取權限。

你說在 Python 中,我們可以使用 ___ 來表示私有變數和方法,請問 ___ 差在哪裡?

用一個底線 _ 表示私有變數和方法並不會真正的阻止外部存取,只是約定俗成的寫法。
而使用兩個底線 __ 來表示私有變數和方法會真正的封裝,阻止外部存取,並且子類別也無法存取,更能保證封裝性。

請問你能用上面的程式碼繼續舉例抽象的用法給我看嗎?

可以,以下是之前提到的 Car、ElectricCar、GasCar 程式碼使用抽象類別和抽象方法的寫法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from abc import ABC, abstractmethod

class Car(ABC):
  def __init__(self):
    self.__year_model = 0
    self.__make = ""
    self.__speed = 0

  @abstractmethod
  def drive(self):
    pass

class ElectricCar(Car):
  def __init__(self):
    super().__init__()
    self.__battery_type = ""

  def set_battery_type(self, battery_type):
    self.__battery_type = battery_type

  def get_battery_type(self):
    return self.__battery_type

  def drive(self):
    print("Electric car is driving.")

class GasCar(Car):
  def __init__(self):
    super().__init__()
    self.__fuel_type = ""

  def set_fuel_type(self, fuel_type):
    self.__fuel_type = fuel_type

  def get_fuel_type(self):
    return self.__fuel_type

  def drive(self):
    print("Gas car is driving.")

#create object
car = ElectricCar()
car.drive()

car = GasCar()
car.drive()

在這個範例中,Car 類別是一個抽象類別,它定義了一個抽象方法 drive()。ElectricCar 和 GasCar 類別都繼承自 Car 類別,並實現了 drive() 方法。

這樣的設計可以達到封裝的目的,同時也可以達到多型的目的。並且因為 Car 類別是抽象類別,所以不能直接建立 Car 類別的物件,只能用它的子類別來建立物件。

有了繼承的特點為何還需要抽象?感覺不出這兩者有何差別?

繼承的作用是讓子類別可以繼承父類別的屬性和方法,使得子類別可以共用父類別的程式碼,達到代碼重用的目的。這樣可以讓程式碼更簡潔,並且可以讓子類別擁有父類別的特徵。

而抽象則是讓類別有共同的特徵和行為,抽象類別不能被實例化,而是用來被其他類別繼承。抽象類別中可以定義抽象方法(abstract method)普通方法,抽象方法只有定義,沒有實現。在子類別中必須實現抽象方法。這樣可以確保所有子類別都有相同的行為,但實現可能不同。

在車的例子中,我們可以用繼承的方式,讓 ElectricCar 和 GasCar 繼承 Car 的屬性,例如 make、model、year_model。而用抽象的方式,我們可以在父類別 Car 中定義一個 drive 方法,並在子類別中實現 drive 方法,這樣可以確保所有車子都有 drive 這個行為。

實際業務上來說,何時會想用繼承何時會想用抽象?

實際業務上,繼承和抽象會根據實際需求而使用。

  • 繼承:

要繼承另一個類別的特徵和行為時,就可以使用繼承。例如,你有一個車類別,並且你需要創建一個電動車和汽油車類別,這兩個類別都有許多相同的特徵和行為,那麼你可以使用繼承,將車類別的特徵和行為繼承到電動車和汽油車類別中。

  • 抽象:

當你有一些類別需要共用同一個介面或行為時,就可以使用抽象。例如,你有一個車類別,並且你需要創建多種不同類型的車,例如電動車、汽油車、柴油車等,這些車都需要有 drive 這個行為,那麼你可以使用抽象類別來定義一個 drive 方法,並在電動車、汽油車、柴油車等類別中實現 drive 方法,這樣就可以確保所有車都有 drive 這個行為。

總結來說,繼承是用來繼承父類別的特徵和行為,而抽象則是用來確保類別具有共同的特徵和行為。

請問你說的特徵具體來說是指什麼東西?

特徵是指類別所具有的屬性行為,包括資料函式

屬性是指類別中的變數,它們用來儲存類別中的資料。例如在之前的例子中,Car 類別中的屬性有 make、model、year_model。

而行為是指類別中的函式,它們用來定義類別的動作。例如在之前的例子中,Car 類別中的行為有 drive、get_make、get_model 等。

這些屬性和行為就是類別的特徵。

請問物件導向為何叫做物件導向?

物件導向程式語言之所以叫做物件導向是因為它重視物件 (Object) 這個概念。在物件導向程式語言中,程式設計師可以使用類別 (Class) 來定義物件的共性,並使用物件來實例化類別。

物件具有狀態 (state)行為 (behavior),並且可以互相之間進行溝通和互動。透過物件導向程式設計,可以更加形象地模擬實際世界中的事物,並且更加容易維護和擴展程式碼。


comments powered by Disqus