物件導向編程 (Object-Oriented Programming)(019)-多型、抽象、封裝應用與解析

在前一篇文章中,我們已經認識了物件導向編程(OOP)的基礎,並深入學習了 繼承 (Inheritance)

繼承幫助我們重用程式碼並建立層次結構,除了繼承之外,OOP 還有三個同樣重要的特性:

  • 多型 (Polymorphism):相同的操作,能夠根據不同物件而展現不同的行為。
  • 抽象 (Abstraction):隱藏細節,強調必要的行為,幫助我們建立清晰的設計契約。
  • 封裝 (Encapsulation):將資料與方法打包,保護內部細節,僅提供必要的接口給外部使用。

這三個特性讓程式碼更靈活、更安全、更容易維護。

多型 (Polymorphism)

多型是物件導向編程的另一個重要支柱,它允許我們對不同類型的物件執行相同的操作。

在Python中,多型可以通過多種方式實現:

  1. 方法覆寫:不同類別可以有同名但行為不同的方法
  2. 運算符重載:同一個運算符可以對不同類型的物件執行不同的操作

多型的一個主要優點是它允許我們編寫更通用、更靈活的代碼,不必關心物件的具體類型。

Different shaped objects passing through same shaped hole, representing polymorphism in programming

多型的例子

不同類別的相同方法名

class Animal:
  def __init__(self, name):
    self.name = name
  def make_noise(self):
    print("{} says, Grrrr".format(self.name))

class Cat(Animal):
  def make_noise(self):
    print("{} says, Meow!".format(self.name))

class Robot:
  def make_noise(self):
    print("beep.boop...BEEEEP!!!")

使用多型

an_animal = Animal("Bear")
my_pet = Cat("Maisy")
my_vacuum = Robot()

objects = [an_animal, my_pet, my_vacuum]

for o in objects:
  o.make_noise()
  
# 輸出:
# "Bear says, Grrrr"
# "Maisy says, Meow!"
# "beep.boop...BEEEEP!!!"

在這個例子中,我們可以調用不同物件的make_noise()方法,而不需要知道它們的具體類型。這就是多型的力量!

Dunder方法和運算符重載

Dunder方法(雙下劃線方法)是Python中的特殊方法,允許我們自定義類別的行為。例如:

  • __init__(): 構造函數
  • __repr__(): 字符串表示
  • __add__(): 加法運算符
  • __len__(): 長度函數

通過定義這些方法,我們可以實現運算符重載,使運算符對不同類型的物件有不同的行為。

class Animal:
  def __init__(self, name):
    self.name = name
    
  def __repr__(self):
    return self.name
    
  def __add__(self, another_animal):
    return Animal(self.name + another_animal.name)

a1 = Animal("Horse")
a2 = Animal("Penguin")
a3 = a1 + a2

print(a1)  # 輸出: Horse
print(a2)  # 輸出: Penguin
print(a3)  # 輸出: HorsePenguin

在這個例子中,我們定義了__add__()方法,使得可以使用+運算符將兩個Animal物件”加”在一起,創建一個新的Animal物件,其名字是兩個原始物件名字的組合。

抽象 (Abstraction)

抽象是物件導向編程的第三個支柱,它幫助我們隱藏複雜性,只展示必要的部分。

在Python中,我們可以使用抽象基類(Abstract Base Classes,ABC)來實現抽象。抽象類不能被實例化,它們只是作為其他類別的模板。

from abc import ABC, abstractmethod

class Animal(ABC):
  def __init__(self, name):
    self.name = name
    
  @abstractmethod
  def make_noise(self):
    pass

class Cat(Animal):
  def make_noise(self):
    print("{} says, Meow!".format(self.name))

在這個例子中,Animal是一個抽象類,它定義了所有動物應該有的基本行為(make_noise),但沒有實現它。子類別(如Cat)必須實現這個方法,否則會出錯。

Abstract representation of a blueprint or template with details hidden

抽象的優勢

強制一致性

抽象類可以確保所有子類別實現特定的方法,保持API的一致性。在我們的例子中,所有Animal的子類別都必須實現make_noise()方法。

定義契約

抽象類定義了一個”契約”,規定了子類別必須遵循的行為。這有助於確保代碼的正確性和可靠性。

提高設計質量

抽象鼓勵我們思考類別的核心行為和責任,而不是具體的實現細節。這有助於創建更好的類別層次結構。

抽象是一個強大的工具,可以幫助我們設計更加模組化、可維護的程式。通過定義抽象類別,我們可以確保類別層次結構的一致性和正確性。

封裝 (Encapsulation)

封裝是物件導向編程的第四個支柱,它涉及將數據和方法打包成一個單元,並限制對內部細節的訪問。

在許多語言中,有三種訪問修飾符:

  • 公共(Public):可以從任何地方訪問
  • 受保護(Protected):只能從同一模塊訪問
  • 私有(Private):只能從定義它們的類別內部訪問

Python沒有嚴格的訪問控制,但有一些約定:

  • 單下劃線(_x):表示受保護的成員
  • 雙下劃線(__x):表示私有成員(名稱會被修改)

封裝幫助我們隱藏實現細節,只暴露必要的接口。這樣,我們可以在不影響用戶代碼的情況下更改內部實現。

A locked box with some visible and some hidden components, representing encapsulation in programming

Getter、Setter和Deleter

Getter、Setter和Deleter是實現封裝的一種方式,它們允許我們控制對類別屬性的訪問和修改。

class Animal:
  def __init__(self, name):
    self._name = name
    self._age = None
    
  def get_age(self):
    return self._age
    
  def set_age(self, new_age):
    if isinstance(new_age, int):
      self._age = new_age
    else:
      raise TypeError
      
  def delete_age(self):
    print("_age Deleted")
    del self._age

在這個例子中,我們使用getter(get_age)、setter(set_age)和deleter(delete_age)來控制對_age屬性的訪問、修改和刪除。setter還包含驗證邏輯,確保年齡只能是整數。

結語

至此,我們已經完整認識了 OOP 的四大支柱:繼承、多型、抽象、封裝
掌握這些概念後,你不僅能寫出可以運行的程式,更能設計出 模組化、可維護、可擴展 的架構。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

返回頂端