物件導向

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

物件導向程式語言 (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 程式碼來實現這個類別可能長得像下面這樣:

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 類別中加入新的屬性和方法。

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 方法來實現多型的用法。

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 屬性改成私有變數:

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 程式碼使用抽象類別和抽象方法的寫法:

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) ,並且可以互相之間進行溝通和互動。透過物件導向程式設計,可以更加形象地模擬實際世界中的事物,並且更加容易維護和擴展程式碼。