面向物件程式設計/抽象
如果你曾經用咖啡機衝過咖啡,你可能就體驗過抽象。當你衝咖啡時,你會確保有足夠的咖啡豆,你會加水,加牛奶,也許還會放一個新的咖啡過濾器。然後,你按下按鈕,你想要的咖啡就做好了。這是一個很好的抽象示例,因為使用者不需要關心特定型別咖啡所需的牛奶量,或需要研磨多少咖啡豆才能得到正確的量,或水溫需要多少度,使用者只需要關心提供必要的材料來生產最終產品。這種結構也適用於面向物件程式設計。物件的使用者應該只關心提供最終產品所需的必要引數,而不應該真正知道或需要知道物件內部是如何工作的。
在一些語言中,可以建立一個不能例項化的類。這意味著我們不能直接使用這個類來建立物件——我們只能從這個類繼承,並使用子類來建立物件。
我們為什麼要這樣做?有時我們想指定一個物件需要具有的屬性集,以便它適合某些任務——例如,我們可能編寫了一個函式,它期望它的一個引數是一個具有某些方法的物件,我們的函式需要使用這些方法。我們可以建立一個類,透過定義這些物件必須實現的方法列表,來作為適合物件的模板。這個類不打算被例項化,因為我們所有的方法定義都是空的——所有方法的內部必須在子類中實現。
抽象類因此是一個介面定義——一些語言也有一種稱為介面的結構型別,它非常相似。如果一個類繼承自指定該介面的類,我們就說該類實現了該介面。
在 Python 中,我們無法阻止任何人例項化一個類,但我們可以透過在方法定義中使用NotImplementedError來建立類似於抽象類的東西。例如,以下是一些可以用作形狀模板的“抽象”類
class Shape2D:
def area(self):
raise NotImplementedError()
class Shape3D:
def volume(self):
raise NotImplementedError()
任何二維形狀都有面積,任何三維形狀都有體積。計算面積和體積的公式取決於我們擁有什麼形狀,不同形狀的物件可能具有完全不同的屬性。
如果一個物件繼承自 2DShape,它將獲得該類的預設面積方法——但預設方法會引發一個錯誤,向用戶明確表明必須在子物件中定義一個自定義方法
class Square(Shape2D):
def __init__(self, width):
self.width = width
def area(self):
return self.width ** 2