跳轉到內容

面向物件程式設計 (OOP) 的關鍵特徵

來自 Wikibooks,開放世界中的開放書籍

試卷 1 - ⇑ 程式設計基礎 ⇑

← 面向物件程式設計的要素 面向物件程式設計的特徵 面向物件程式設計中的設計原則 →


OOP 為程式增加了額外的結構。我們引入了兩個新的結構特徵:物件和類。我們還了解了普通變數和過程如何獲得額外的規則,並被重新命名為屬性和方法。OOP 語言通常也具有普通變數和過程,但我們主要使用面向物件的版本;這就是我們所說的“面向物件” - 我們正在積極地在程式設計和實現中使用物件。

這種額外的結構使我們能夠在程式設計中做一些用普通面向過程的程式設計無法做到的事情。在後面的部分,我們將看到這些如何幫助構建更大更復雜的程式,同時減少錯誤數量,並使它們更容易使用。

例項化和建構函式

[編輯 | 編輯原始碼]

我們有兩個新的結構化概念:類和物件。我們如何建立它們?

類是一種新的資料型別,因此我們在原始碼中指定它。通常,我們建立一個新的原始檔,並將其命名為我們想要為類命名的名稱。因此,“汽車”類可以用名為“Car.src”的原始檔描述(在 Java 中:“Car.java”,在 Python 中:“Car.py”等)。

但是我們如何建立新的物件 - 我們如何使用類作為模板為我們建立多個物件?

這個過程被稱為例項化:當程式執行時,我們呼叫類中的一種特殊方法(即過程)來例項化一個新物件。(建立的物件被稱為類的例項)這些特殊方法與普通方法相同,但在某些語言中它們具有不同的語法或額外的標籤,以明確它們是用於建立新物件的。

因為這些方法具有特殊目的,為了更容易地談論它們,而不會與物件上的通用方法混淆,我們為可以例項化新物件的方法起了特殊的名字。這些方法被稱為建構函式。

回顧

  • 建構函式是一種方法。它具有建立(例項化)新物件的額外功能。
  • 方法是一種過程。它具有必須是類和/或物件的一部分的額外規則。
  • OOP 中的過程與非 OOP 語言中的過程相同。
示例:例項化
dim polo as new car   'instantiation 1
dim escort as new car 'instantiation 2

上面的程式碼建立了名為 polo 和 escort 的物件,它們都是 car 類型別的(我們之前宣告過)。我們現在可以使用所有公共屬性和方法

polo.refuel(100) 'assuming fuel starts at 0
polo.drive()
escort.refuel(50) 'assuming fuel starts at 0
escort.drive()
for x as integer = 1 to 20
  escort.drive()
  polo.drive()
next
polo.refuel(10)
console.writeline("polo: " & polo.getFuel())
console.writeline("escort: " & escort.getFuel())

這將輸出以下內容

   程式碼輸出

加油!
加油!
加油!
polo: 89
escort: 29

練習:例項化一輛汽車
編寫你自己的甲殼蟲汽車的例項化

答案

dim beetle as new car 'instantiation

以下內容將輸出什麼

dim ka as new car
dim montego as new car
ka.refuel(10) 'assuming fuel starts at 0
montego.refuel(50) 'assuming fuel starts at 0
montego.drive()
montego.drive()
for x = 0 to 10
  montego.drive()
next
ka.refuel(10)
console.writeline("ka: " & ka.getFuel())
console.writeline("montego: " & montego.getFuel())

答案

   程式碼輸出

加油!
加油!
加油!
ka: 20
montego: 37

多型性

[編輯 | 編輯原始碼]

在面向過程的程式設計中,一個方法可用的資料通常對所有方法都可用。這可以手動限制,例如使用私有/受保護/等,參見封裝 [此處需要連結],但這可選。通常唯一的限制是提供給每個過程的資料必須匹配特定的 A-level 計算機/AQA/試卷 1/程式設計基礎/資料型別。在定義過程時,我們選擇它將接受哪些資料型別(即我們限制了 引數)。

示例:---資料型別:2 個過程,例如 int int 和 string int

當有人嘗試使用過程時,計算機會檢視提供的資料,並將資料型別與 ....(只要他們提供的資料型別與我們選擇的資料型別完全匹配,過程就會執行。)

在面向物件的程式設計中,所有資料預設情況下都封裝在 [出於多種原因 - 需要連結]。與面向過程的程式設計不同,在面向過程的程式設計中,任何過程都可以訪問程式中任何地方的任何資料,而方法只能直接訪問其自身物件中的資料。要訪問不同物件中的資料,我們必須將整個物件傳遞給需要對該資料進行操作的任何方法(或過程)。

這會產生一個問題:引數的資料型別現在將是物件類的型別,每個類都是一個唯一的資料型別。對於簡單的案例,這工作得很好,但對於更大的問題,它會阻止我們重複使用方法。這對 OOP 將是災難性的:我們將需要不斷地從一個類複製/貼上方法到另一個類,調整引數型別,而不是重複使用程式碼。這將是程式設計簡便性和減少錯誤方面的倒退。

示例:多型性

例如,如果我們有一個 Pet 類,它具有 Year_of_Birth 變數,還有一個 Owner 類,它也具有 Year_of_Birth 變數,我們有一個方法可以根據 Year_of_Birth 計算當前年齡......它要麼可以作用於 Pet 類,要麼可以作用於 Owner 類,但它不能同時作用於兩者。

為了解決這個問題,OOP 語言有一個被稱為 多型性 的基本特性。多型性有很多種,但大多數情況下我們只使用其中兩種。共同的概念是,一個事物可以假裝是多個事物。

特設多型性

[編輯 | 編輯原始碼]

多型性的最簡單形式是 w:特設多型性,當程式設計師編寫過程的多個不同版本時:例如一個接受型別 A 的物件,一個接受型別 B 的物件。這兩個版本具有相同的名稱,OOP 語言知道將它們視為相同,但根據過程在執行時呼叫的方式,智慧地使用其中一個或另一個。

子型別多型性

[編輯 | 編輯原始碼]

OOP 中大量使用的更強大的形式是 w:子型別化。使用子型別多型性,程式設計師將不同的資料型別相互關聯,向計算機承諾 - 在某種程度上 - 這些資料型別可以互換使用。這種機制是繼承(見下文)。

示例:多型性

再次考慮我們的汽車示例。當我們建立 electricCar 物件時,我們從 car 繼承了,並添加了 numBatteries 屬性和方法。但是當我們嘗試加油時會發生什麼,讓我們看一下程式碼

public sub refuel(byVal x as integer) as integer
  console.writeline("pumping gas!")
  fuel = fuel + x
end sub

這不行。我們正在建立一輛電動汽車,我們不想說我們正在加油;我們穿著涼鞋吃酸奶的朋友會怎麼說!所以對於 electricCar 物件,我們想要繼承 car 中的所有內容,但我們想要改變 refuel 方法。為了做到這一點,我們將使用被稱為 重寫 的東西

class electricCar
  inherits car
  private numBatteries as integer
  public sub setnumBatteries(byVal n as integer)
    numBatteries = n
  end sub
  public function getnumBatteries() as integer 'interface
    return numBatteries
  end sub
  '###### overrides morphs the inherited version, replacing it
  public overrides sub refuel(byVal x as integer)
    console.writeline("pure renewable energy!")
    fuel = fuel + x
  end sub
  '######
end class
練習:多型性
在多型性中,我們使用什麼關鍵字來重新定義子例程

答案

重寫

編寫一個使用多型性的豪華轎車類的定義,確保最大速度不超過 100

答案

class limo
  inherits car
  public overrides sub setSpeed(byVal s as integer)
    if s > 100 then
      maxSpeed = 100
    else
      maxSpeed = s
    end if
  end sub
end class
編寫一個肌肉車的定義,它繼承了汽車,但每次行駛時使用 30 個單位的燃料,並顯示“vroom vroom!”。它還應儲存有關真皮座椅的詳細資訊,允許你與之互動。

答案

class musclecar 
  inherits car
  private leatherSeating as boolean
  public overrides sub drive()
    fuel = fuel - 30
    console.writeline("vroom vroom!")
  end sub
  public sub setLeather(s as boolean)
    leatherSeating = s
  end sub
  public function getLeather()
    return leatherSeating
  end function
end class
描述多型性

答案

多型性允許你從父類繼承屬性,但重新定義某些方法或屬性

在實踐中,上面的多型性的簡單版本如果我們最終為每種型別編寫了不同版本的類似方法,並不會節省很多程式設計師的時間。

為了解決這個問題,OOP 語言使用繼承(如果型別是作為類實現的,也稱為子類化)。

當一個類從另一個類繼承時,它會採用另一個類的型別,並採用所有方法和屬性。新類可以被視為舊類 - 過程不需要知道它們是看到一個新類還是一箇舊類。

原始類通常被稱為超類,而新類(繼承)類被稱為子類。

至關重要的是,子類被允許在繼承的屬性和方法之上新增額外的屬性和方法。因為這些新功能擴充套件了超類的行為,所以我們通常說子類“擴充套件”了超類。在某些 OOP 語言中,建立子類的語法是使用關鍵字“extends”,以及你想要從中繼承的超類。

示例:繼承

例如,Shape 類可以定義任何形狀共有的變數和方法(例如頂點數、顏色、位置),這些變數和方法由子類繼承,然後在適當的情況下訪問相同的程式碼,同時提供子類特定的面積方法 - 請參見下面的覆蓋。

示例:繼承

例如,theShape.area() 將呼叫 theShape 所屬類的某個方法,當 theShape 是多邊形或橢圓的例項時,這將是不同的方法。

子型別是指型別的層次結構,其中 ellipsepolygon 都是超型別 Shape 的子型別。使用的方法可以在執行時選擇(在某些語言中),因此呼叫 theShape.area() 的程式碼不必知道 theShape 屬於什麼子型別,只要它提供了一個名為 area 的方法即可。

然而,這樣做要付出代價 - 超類的更改可能會在某些子類中產生意想不到的副作用。

練習:繼承
宣告一個名為 limo 的新類,該類具有 numSeats 和 colourSeats 屬性;並能夠與它們互動

答案

class limo
  inherits car 'declares what attributes and methods you are inheriting
  private numSeats as integer 'must be private
  private colourSeats as integer 'must be private
  public sub setnumSeats(byVal s as integer) 'interface set
    numSeats = s
  end sub
  public function getnumSeats() 'interface get
    return numSeats
  end function
  public sub setcolourSeats(byVal c as string) 'interface set
    colourSeats = c
  end sub
  public function getcolourSeats() 'interface get
    return colourSeats 
  end function
end class
使用繼承的好處是什麼?

答案

從父類建立新類非常快且容易。它允許以模組化方式建立類,在這種情況下,你可能根本不會使用基類。

練習:繼承
車輛的繼承圖,所有車輛都共享父類“車輛”的屬性和功能。請注意箭頭方向。

基於上面的汽車示例,如果我們想宣告一輛電動汽車會發生什麼?嗯,我們可能想儲存一些關於它有多少電池的資訊。

class electricCar
  private maxSpeed as integer
  private fuel as integer 'fixed!
  private numBatteries as integer 'added
  public sub setnumBatteries(byVal n as integer)
    numBatteries = n
  end sub
  public function getnumBatteries()
    return numBatteries
  end sub
  public sub setSpeed(byVal s as integer)
    maxSpeed = s
  end sub
  public function getSpeed() as integer
    return maxSpeed
  end function
  public sub refuel(byVal x as integer) as integer
    '.....
  'HOLD ON! 
end class

這似乎是一項非常漫長而乏味的任務,需要重新編寫所有相同的程式碼。你說得對!如果我們只需要宣告所有我們想新增的新內容,那就好多了。OOP 允許繼承,其中一個新類可以繼承父類的屬性和方法。

class electricCar
  inherits car 'declares what attributes and methods you are inheriting
  private numBatteries as integer 'added
  public sub setnumBatteries(byVal n as integer) 'interface
    numBatteries = n
  end sub
  public function getnumBatteries() 'interface
    return numBatteries
  end function
end class

這意味著 electricCar 現在可以訪問 car 宣告的所有內容,以及新的 numBatteries 屬性和方法。讓我們例項化這個例子,看看可能發生什麼。

dim gwiz as new electricCar
gwiz.setnumBatteries(6) 'from electricCar
gwiz.setSpeed(60)       'from car
gwiz.drive()            'from car

覆蓋

[edit | edit source]

繼承(又名子類化)和多型性在很大程度上解決了方法和屬性比普透過程和變數更受限制的問題。但我們很快發現,我們不僅想要擴充套件超類(即向其現有方法/屬性新增內容),而且還想要修改其現有行為。

這非常有用,以至於大多數 OOP 語言都將其作為核心功能支援,這稱為覆蓋。

當一個子類擴充套件超類時,它可以選擇用新的自定義版本替換超類的任何方法。這種替換行為稱為覆蓋,舊版本被稱為被覆蓋。子類中的替換方法可以(但不必)呼叫超類的覆蓋方法,以及執行子類所需的額外操作。

過載

[edit | edit source]

大多數 OO 語言允許重新定義標準算術運算子 (+,-,*,/,...),以便它們可以應用於類的成員,或者應用於類和標準變數型別(實數、整數,..)。與多型性一樣,適當的方法(必須是類定義的一部分)可以在執行時選擇。

例如,表示複數的類 Complex 可以為其例項實現標準算術運算。

從技術上講,過載是一種 特定多型性,但它被廣泛使用,因此擁有自己的名稱。

示例

[edit | edit source]
華夏公益教科書