Golang通脈之面向對象


面向對象的三大特征:

  1. 封裝:隱藏對象的屬性和實現細節,僅對外提供公共訪問方式
  2. 繼承:使得子類具有父類的屬性和方法或者重新定義、追加屬性和方法等
  3. 多態:不同對象中同種行為的不同實現方式

Go並不是一個純面向對象的編程語言。在 Go 語言中可以使用結構體struct對屬性進行封裝,結構體就像是類的一種簡化形式。可以在結構體上添加捆綁數據和方法的行為,這些數據和方法與類類似

Go語言沒有“類”的概念,也不支持“類”的繼承等面向對象的概念。Go語言中通過結構體的內嵌再配合接口比面向對象具有更高的擴展性和靈活性。

結構體和方法

type Employee struct {  
    FirstName   string
    LastName    string
    TotalLeaves int
    LeavesTaken int
}

func (e Employee) LeavesRemaining() {  
    fmt.Printf("%s %s has %d leaves remaining", e.FirstName, e.LastName, (e.TotalLeaves - e.LeavesTaken))
}

func main() {  
    e := employee.Employee {
        FirstName: "Sam",
        LastName: "Adolf",
        TotalLeaves: 30,
        LeavesTaken: 20,
    }
    e.LeavesRemaining()
}

New()函數

Java語言中提供了構造方法創建並初始化對象,在Go語言中一般需要自己實現一個對外可見的New函數

func main() {  
    var e Employee
    e.LeavesRemaining()
}

運行結果:

has 0 leaves remaining

通過運行結果可以知道,使用Employee的零值創建的變量是不可用的。它沒有有效的名、姓,也沒有有效的保留細節。在其他的OOP語言中,比如java,這個問題可以通過使用構造函數來解決。使用參數化構造函數可以創建一個有效的對象。

go不支持構造函數。如果某個類型的零值不可用,則程序員的任務是不導出該類型以防止其他包的訪問,並提供一個名為NewT(parameters)的函數,該函數初始化類型T和所需的值。在go中,它是一個命名一個函數的約定,它創建了一個T類型的值給NewT(parameters)。這就像一個構造函數。如果包只定義了一個類型,那么它的一個約定就是將這個函數命名為New(parameters)而不是NewT(parameters)

首先修改employee結構體為非導出,並創建一個函數New(),它將創建一個新Employee:

type employee struct {  
    firstName   string
    lastName    string
    totalLeaves int
    leavesTaken int
}

func New(firstName string, lastName string, totalLeave int, leavesTaken int) employee {  
    e := employee {firstName, lastName, totalLeave, leavesTaken}
    return e
}

func (e employee) LeavesRemaining() {  
    fmt.Printf("%s %s has %d leaves remaining", e.firstName, e.lastName, (e.totalLeaves - e.leavesTaken))
}

在這里做了一些重要的改變。已經將Employee struct的起始字母e設置為小寫。

由於employee是未導出的,所以不可能從其他包中創建類型employee的值。因此,提供了一個輸出的新函數。將所需的參數作為輸入並返回新創建的employee

func main() {  
    e := employee.New("Sam", "Adolf", 30, 20)
    e.LeavesRemaining()
}

運行結果:

Sam Adolf has 10 leaves remaining 

因此,雖然Go不支持類,但是結構體可以有效地使用,在使用構造函數的位置,使用New(parameters)的方法即可。


組合與繼承

在 Go 語言中沒有 extends 關鍵字,它使用在結構體中內嵌匿名類型的方法來實現繼承。在Go語言中稱之為組合(Composition)。組合的一般定義是“放在一起”。

舉一個博客文章例子:每個博客都有標題、內容和作者信息。這可以用組合完美地表示出來。

嵌入結構體實現組合

可以通過將一個struct類型嵌入到另一個結構中實現:

/*
創建了一個author struct,它包含字段名、lastName和bio。添加了一個方法fullName(),將作者作為接收者類型,這將返回作者的全名。
*/
type author struct {  
    firstName string
    lastName  string
    bio       string
}

func (a author) fullName() string {  
    return fmt.Sprintf("%s %s", a.firstName, a.lastName)
}
/*
post struct有字段標題、內容。它還有一個嵌入式匿名字段作者。這個字段表示post struct是由author組成的。現在post struct可以訪問作者結構的所有字段和方法。還在post struct中添加了details()方法
*/
type post struct {  
    title     string
    content   string
    author
}

func (p post) details() {  
    fmt.Println("Title: ", p.title)
    fmt.Println("Content: ", p.content)
    fmt.Println("Author: ", p.author.fullName())
    fmt.Println("Bio: ", p.author.bio)
}

func main() {  
    author1 := author{
        "Naveen",
        "Ramanathan",
        "Golang Enthusiast",
    }
    post1 := post{
        "Inheritance in Go",
        "Go supports composition instead of inheritance",
        author1,
    }
    post1.details()
}

運行結果:

Title:  Inheritance in Go  
Content:  Go supports composition instead of inheritance  
Author:  Naveen Ramanathan  
Bio:  Golang Enthusiast  

嵌入結構體的切片

在以上程序的main函數下增加以下代碼,並運行

type website struct {  
        []post
}
func (w website) contents() {  
    fmt.Println("Contents of Website\n")
    for _, v := range w.posts {
        v.details()
        fmt.Println()
    }
}

運行報錯:

syntax error: unexpected [, expecting field name or embedded type 

這個錯誤指向structs []post的嵌入部分。原因是切片不能當做匿名字段使用。需要一個字段名

type website struct {  
        posts []post
}

修改完完整代碼如下:

type author struct {  
    firstName string
    lastName  string
    bio       string
}

func (a author) fullName() string {  
    return fmt.Sprintf("%s %s", a.firstName, a.lastName)
}

type post struct {  
    title   string
    content string
    author
}
func (p post) details() {  
    fmt.Println("Title: ", p.title)
    fmt.Println("Content: ", p.content)
    fmt.Println("Author: ", p.fullName())
    fmt.Println("Bio: ", p.bio)
}

type website struct {  
 	posts []post
}
func (w website) contents() {  
    fmt.Println("Contents of Website\n")
    for _, v := range w.posts {
        v.details()
        fmt.Println()
    }
}
func main() {  
    author1 := author{
        "Naveen",
        "Ramanathan",
        "Golang Enthusiast",
    }
    post1 := post{
        "Inheritance in Go",
        "Go supports composition instead of inheritance",
        author1,
    }
    post2 := post{
        "Struct instead of Classes in Go",
        "Go does not support classes but methods can be added to structs",
        author1,
    }
    post3 := post{
        "Concurrency",
        "Go is a concurrent language and not a parallel one",
        author1,
    }
    w := website{
        posts: []post{post1, post2, post3},
    }
    w.contents()
}   

運行結果:

Contents of Website

Title:  Inheritance in Go  
Content:  Go supports composition instead of inheritance  
Author:  Naveen Ramanathan  
Bio:  Golang Enthusiast

Title:  Struct instead of Classes in Go  
Content:  Go does not support classes but methods can be added to structs  
Author:  Naveen Ramanathan  
Bio:  Golang Enthusiast

Title:  Concurrency  
Content:  Go is a concurrent language and not a parallel one  
Author:  Naveen Ramanathan  
Bio:  Golang Enthusiast  

接口與多態

Go中的多態性(Polymorphism)是在接口的幫助下實現的。接口可以在Go中隱式地實現。如果類型為接口中聲明的所有方法提供了定義,則該類型實現了這個接口。

任何定義接口所有方法的類型都被稱為隱式地實現該接口。

類型接口的變量可以保存實現接口的任何值。接口的這個屬性用於實現Go中的多態性。

定義一個正方形 Square 和一個長方形 Rectangle

// 正方形
type Square struct {
    side float32
}

// 長方形
type Rectangle struct {
    length, width float32
}

計算這兩個幾何圖形的面積。但由於他們的面積計算方式不同,需要定義兩個不同的 Area() 方法。

於是,可以定義一個包含 Area() 方法的接口 Shaper,讓 SquareRectangle 都實現這個接口里的 Area()

// 接口 Shaper
type Shaper interface {
    Area() float32
}

// 計算正方形的面積
func (sq *Square) Area() float32 {
    return sq.side * sq.side
}

// 計算長方形的面積
func (r *Rectangle) Area() float32 {
    return r.length * r.width
}

main() 函數中調用 Area()

func main() {
    r := &Rectangle{10, 2}
    q := &Square{10}

    // 創建一個 Shaper 類型的數組
    shapes := []Shaper{r, q}
    // 迭代數組上的每一個元素並調用 Area() 方法
    for n, _ := range shapes {
        fmt.Println("圖形數據: ", shapes[n])
        fmt.Println("它的面積是: ", shapes[n].Area())
    }
}

/*Output:
圖形數據:  &{10 2}
它的面積是:  20
圖形數據:  &{10}
它的面積是:  100
*/

由以上代碼輸出結果可知:不同對象調用 Area() 方法產生了不同的結果,展現了多態的特征。

總結

  • 面向對象的三大特征是:封裝、繼承和多態
  • Go 語言使用結構體對屬性進行封裝,結構體就像是類的一種簡化形式
  • 在 Go 語言中,方法是作用在接收者(receiver)上的一個函數,接收者是某種類型的變量
  • 名稱首字母的大小寫決定了該變量/常量/類型/接口/結構/函數……能否被外部包導入
  • 無法被導入的字段可以使用 gettersetter 的方式來訪問
  • Go 語言使用在結構體中內嵌匿名類型的方法來實現繼承
  • 使用接口可以實現多態


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM