當我們談論程式設計時,我們通常指的是編寫一堆修改某些資料並與某些資料互動的函數。物件導向程式設計(OOP)是一種程式設計模型,它專注於包含資料並附加一些相關功能的「物件」。物件導向程式設計有四大支柱:繼承、封裝、多態性與抽象。在本部落格中,我們將透過範例了解如何在 Golang 中實現它們中的每一個。推薦一些有關 OOP 的基本概念,但如果沒有,我將簡要介紹所有四個支柱的含義。
物件導向程式設計的核心思想可以概括為以下幾點:
讓我們來看一些 Golang 中的程式碼來理解這三個概念:
package main import "fmt" type Batman struct { actor string year int } func (b Batman) SayImBatman() { fmt.Printf("I'm %s and I'm Batman from year %d\n", b.actor, b.year) } func main() { b1 := Batman{actor: "Michael Keaton", year: 1989} b2 := Batman{actor: "Christian Bale", year: 2005} b1.SayImBatman() b2.SayImBatman() }
在Golang中,類別只不過是我們定義的型別。這些類型不一定必須是結構體,但通常都是結構體,因為在 OOP 中我們使用資料集合,這些資料可以是任何類型(字串、整數等)。
類別是物件的藍圖。每當實例化一個類別時,就會形成一個物件。在此範例中,b1 和 b2 是 Batman 類別的物件。
SayImBatman 函數可以在該類別的任何物件上呼叫。由於它與 Batman 類別相關,因此不稱之為常規函數,而是稱為類別的方法。
我認為這應該足以讓您清楚 OOP 的基礎知識,以便您繼續進行下一部分,我們將在其中介紹 OOP 的四大支柱。
繼承引入了 OOP 中 parent 和 child 類別的概念。子類別是從父類別派生的類,並繼承其所有方法和屬性(資料)。讓我們來看看一些程式碼來幫助我們理解這一點:
package main import "fmt" type Hero struct { team string } type Batman struct { Hero name string } type Ironman struct { Hero power int } func (h Hero) SayTeam() { fmt.Println("My Team is", h.team) } func (b Batman) SayImBatman() { fmt.Printf("I'm %s and I'm Batman\n", b.name) } func (i Ironman) SayPowerLevel() { fmt.Printf("I'm Ironman and my powerlevel is %d\n", i.power) } func main() { b1 := Batman{Hero{team: "Justice League"}, "Christian Bale"} i1 := Ironman{Hero{team: "Avengers"}, 23} b1.SayImBatman() b1.SayTeam() i1.SayPowerLevel() i1.SayTeam() }
在此範例中,蝙蝠俠和鋼鐵人是 Hero 父類的子類別。他們可以存取其父類別的屬性(即 team)及其方法(即 SayTeam)。正如您在聲明 b1 和 i1 實例時所看到的那樣,我們指定了父類別屬性以及它們各自類別的特定屬性。它們都能夠呼叫父類別中定義的 SayTeam 方法。但它們也有各自獨特的屬性和方法。
Golang 使用組合(在結構體中使用結構體)來實現繼承。它不像其他 OOP 語言(例如 C 或 Java)那樣具有內建的基於類別的繼承。
封裝是隱藏物件內部屬性且不允許直接修改它們的原理。相反,它依賴於提供獲取和更新這些屬性的方法。讓我們看一個例子來更好地理解這一點:
package main import "fmt" type Batman struct { actor string year int } func (b Batman) SayImBatman() { fmt.Printf("I'm %s and I'm Batman from year %d\n", b.actor, b.year) } func main() { b1 := Batman{actor: "Michael Keaton", year: 1989} b2 := Batman{actor: "Christian Bale", year: 2005} b1.SayImBatman() b2.SayImBatman() }
package main import "fmt" type Hero struct { team string } type Batman struct { Hero name string } type Ironman struct { Hero power int } func (h Hero) SayTeam() { fmt.Println("My Team is", h.team) } func (b Batman) SayImBatman() { fmt.Printf("I'm %s and I'm Batman\n", b.name) } func (i Ironman) SayPowerLevel() { fmt.Printf("I'm Ironman and my powerlevel is %d\n", i.power) } func main() { b1 := Batman{Hero{team: "Justice League"}, "Christian Bale"} i1 := Ironman{Hero{team: "Avengers"}, 23} b1.SayImBatman() b1.SayTeam() i1.SayPowerLevel() i1.SayTeam() }
在Golang中,從套件中導出的屬性和方法以大寫字母開頭。當我們在utils包中定義小寫的actor和year時,我們確保它們不能被直接修改。相反,就像您在 main.go 檔案中看到的那樣,您需要使用匯出的方法(以大寫字母開頭) - GetActor、SetActor 等來獲取和修改它們。
這就是封裝的全部內容 - 確保防止資料意外更改,並提供與資料安全互動的方法。
您會注意到不同的一件事是,在 Batman 類別的所有方法中,我們使用指標接收器 *Batman 而不是像前面範例中那樣使用值接收器 Batman。這是因為我們希望能夠在 Set 方法中修改原始結構。在 Golang 中,最佳實踐是,如果某些方法需要指標接收器,則讓所有方法都使用指標接收器以保持一致性。這就是為什麼 Get 方法也使用指標接收器,即使它們沒有修改原始結構。
此外,還要注意的一件事是,僅僅因為我們使用的是指標接收器,我們就不必這樣做:(&b1).GetActor。在 Golang 中,帶有指針參數的函數必須採用指針,但帶有指針接收器的方法可以採用值或指針作為接收器。
TL;DR:Golang 自動將b1.GetActor 轉換為(&b1).GetActor,因為GetActor 方法有一個指標接收器,但如果GetActor 是一個普通函數,它不會將GetActor(b1) 轉換為GetActor( &b1)一個指標參數。
OOP 的接下來兩個支柱可以合併在一起,因為它們的程式碼範例看起來非常相似。多態性是指兩個不同類別的兩個不同物件可以被視為同一公共超類別的物件的程式設計實踐。這意味著您可以在兩個不同的物件上呼叫相同的函數,就好像它們是同一類別的物件一樣。這應該開始讓您感受到所涉及的介面的味道:)
讓我們看一些程式碼來更好地理解這一點:
package main import "fmt" type Batman struct { actor string year int } func (b Batman) SayImBatman() { fmt.Printf("I'm %s and I'm Batman from year %d\n", b.actor, b.year) } func main() { b1 := Batman{actor: "Michael Keaton", year: 1989} b2 := Batman{actor: "Christian Bale", year: 2005} b1.SayImBatman() b2.SayImBatman() }
在此範例中,StartFight 函數可以傳遞 b1 和 i1 對象,即使它們彼此沒有任何關係。試著理解這與繼承有何不同,在繼承中,子類別可以存取父類別的方法。在此範例中,沒有子類別和父類別(也沒有共用方法)。相反,兩個不同的物件被函數視為相同:這稱為多態性。
現在,這也可以被視為抽象的例子。顧名思義,抽像是一種隱藏實作細節,而只是提供為您處理事務的函數的程式設計實踐。在這個例子中,你不需要關心單一英雄的方法是如何被配置的。當您想使用任何英雄的戰鬥功能時,您可以隨時繼續使用開始戰鬥功能。這樣,實作細節對使用者來說是隱藏的,只暴露基本的細節。
現在回到多態,有兩個更常見的例子,那就是方法重寫和重載。
方法重寫是指子類別定義自己的父類別中定義的方法的實作。現在使用此實作來代替原始父類別的實作。讓我們看看之前用於繼承的程式碼,看看它在方法重寫後的樣子:
package main import "fmt" type Hero struct { team string } type Batman struct { Hero name string } type Ironman struct { Hero power int } func (h Hero) SayTeam() { fmt.Println("My Team is", h.team) } func (b Batman) SayImBatman() { fmt.Printf("I'm %s and I'm Batman\n", b.name) } func (i Ironman) SayPowerLevel() { fmt.Printf("I'm Ironman and my powerlevel is %d\n", i.power) } func main() { b1 := Batman{Hero{team: "Justice League"}, "Christian Bale"} i1 := Ironman{Hero{team: "Avengers"}, 23} b1.SayImBatman() b1.SayTeam() i1.SayPowerLevel() i1.SayTeam() }
程式的輸出是:
//oops-in-go/utils/utils.go package utils type Batman struct { actor string year int } func (b *Batman) GetActor() string { return b.actor } func (b *Batman) GetYear() int { return b.year } func (b *Batman) SetActor(actor string) { b.actor = actor } func (b *Batman) SetYear(year int) { b.year = year }
Batman 類別的物件現在使用自己的 SayTeam 方法,而不是父 Hero 類別的方法。由於 Ironman 類別沒有自己的 SayTeam 方法,因此它的物件仍然使用其父類別的方法。這就是方法重寫的意思,子類別「重寫」父類別中定義的方法。
這是指同一個函數能夠接受多個不同的參數。這些參數的數量或類型可能不同。 Golang 提供了兩種方法來實現這一點:透過可變參數函數,另一種透過介面。
讓我們來看看兩者的程式碼,這將幫助您更好地理解:
// oops-in-go/main.go package main import ( "fmt" "oops-in-go/utils" ) func main() { b1 := utils.Batman{} b1.SetActor("Michael Keaton") b1.SetYear(1989) fmt.Printf("I'm %s and I'm Batman from year %d\n", b1.GetActor(), b1.GetYear()) b1.SetActor("Christian Bale") b1.SetYear(2005) fmt.Printf("I'm %s and I'm Batman from year %d\n", b1.GetActor(), b1.GetYear()) }
在這裡,您可以使用任意數量個參數「重載」listMembers函數。
package main import "fmt" type Hero interface { Fight() } type Batman struct { weapon string } type Ironman struct { weapon string } func (b Batman) Fight() { fmt.Printf("Batman hits with a %s\n", b.weapon) } func (i Ironman) Fight() { fmt.Printf("Ironman hits with a %s\n", i.weapon) } func StartFight(h Hero) { fmt.Println("Fight has started.") h.Fight() } func main() { b1 := Batman{"Batarang"} i1 := Ironman{"Repulsor rays"} StartFight(b1) StartFight(i1) }
程式的輸出是:
package main import "fmt" type Hero struct { team string } type Batman struct { Hero name string } type Ironman struct { Hero power int } func (h Hero) SayTeam() { fmt.Println("My Team is", h.team) } func (b Batman) SayImBatman() { fmt.Printf("I'm %s and I'm Batman\n", b.name) } func (i Ironman) SayPowerLevel() { fmt.Printf("I'm Ironman and my powerlevel is %d\n", i.power) } func (b Batman) SayTeam() { fmt.Printf("I'm Batman and my team is %s\n", b.team) } func main() { b1 := Batman{Hero{team: "Justice League"}, "Christian Bale"} i1 := Ironman{Hero{team: "Avengers"}, 23} b1.SayImBatman() b1.SayTeam() i1.SayPowerLevel() i1.SayTeam() }
這裡我們「重載」了 saySomething 方法來接受不同類型的參數。我們採用一個空介面作為參數,它可以是任何類型,然後使用 switch case 檢查其類型並相應地列印輸出。
我很清楚這是一篇很長的文章,如果您堅持讀到最後,我想讓您知道我真的很高興:) 我真誠地希望您學到很多有關面向對象編程的新知識以及如何在Golang 中實現它。我在我的網站上撰寫有關不同技術概念的博客,如果您有興趣學習新事物,我建議您訂閱我的時事通訊。
以上是Golang 中的物件導向程式設計 (OOP) 簡介的詳細內容。更多資訊請關注PHP中文網其他相關文章!