Go基礎語法(十二)

面向對象編程

Go 並不是完全面向對象的編程語言。

Go 官網的 FAQ 回答了 Go 是否是面嚮對象語言,摘錄如下:

可以說是,也可以說不是。雖然 Go 有類型和方法,支持面向對象的編程風格,但卻沒有類型的層次結構。Go 中的“接口”概念提供了一種不同的方法,我們認爲它易於使用,也更爲普遍。Go 也可以將結構體嵌套使用,這與子類化(Subclassing)類似,但並不完全相同。此外,Go 提供的特性比 C++ 或 Java 更爲通用:子類可以由任何類型的數據來定義,甚至是內建類型(如簡單的“未裝箱的”整型)。這在結構體(類)中沒有受到限制。

使用結構體,而非類

Go 不支持類,而是提供了結構體。結構體中可以添加方法。這樣可以將數據和操作數據的方法綁定在一起,實現與類相似的效果。

先來看一個例子,建立如下目錄:


編輯people.go:

package demo

import "fmt"

type Boy struct {
    Name string
    Age int
    Sex string
}

func (b Boy)GetBoy() int{
    fmt.Println(b.Name, b.Age, b.Sex)
    return b.Age
}

一個結構體,掛了一個方法。
編輯main.go

package main

import "oop_demo/demo"

func main()  {
    b := demo.Boy{"allen", 20,"男"}
    b.GetBoy()
}

運行main.go。
以上就是可以用結構體實現面向對象的代碼。

但是,這種方式如果在main中調用方法而未賦值的時候,會自動使用Boy結構體中變量的零值,如果在Java或Python這種OOP語言中,是使用構造器來解決這種問題,Go 並不支持構造器。如果某類型的零值不可用,需要程序員來隱藏該類型,避免從其他包直接訪問。

使用New()函數,而非構造器

更改 people.go:

package demo

import "fmt"

type boy struct {
    name string
    age int
    sex string
}

func New(name string, age int, sex string) boy{
    b := boy {name, age, sex}
    return b
}

func (b boy)GetBoy() int{
    fmt.Println(b.name, b.age, b.sex)
    return b.age
}

更改main.go

package main

import "oop_demo/demo"

func main()  {
    b := demo.New("allen", 20,"男")
    b.GetBoy()
}

運行main.go查看結果與之前相同。

雖然 Go 不支持類,但結構體能夠很好地取代類,而以 New(parameters) 簽名的方法可以替代構造器。

組合取代繼承

Go 不支持繼承,但它支持組合(Composition)。組合一般定義爲“合併在一起”。汽車就是一個關於組合的例子:一輛汽車由車輪、引擎和其他各種部件組合在一起。

1.通過嵌套結構體進行組合
一旦結構體內嵌套了一個結構體字段,Go 可以使我們訪問其嵌套的字段,好像這些字段屬於外部結構體一樣。所以上面第 11 行的 p.author.fullName() 可以替換爲 p.fullName()。於是,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 (p post) details() {  
    fmt.Println("Title: ", p.title)
    fmt.Println("Content: ", p.content)
    fmt.Println("Author: ", p.fullName())
    fmt.Println("Bio: ", p.bio)
}

2.結構體切片的嵌套

package main

import (  
    "fmt"
)

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()
}

多態

Go 通過接口來實現多態。我們已經討論過,在 Go 語言中,我們是隱式地實現接口。一個類型如果定義了接口所聲明的全部方法,那它就實現了該接口。

用接口實現多態

關鍵代碼:

type Income interface {
    calculate() int
    source() string
}

type FixedBilling struct {
    projectName string
    biddedAmount int
}

type TimeAndMaterial struct {
    projectName string
    noOfHours  int
    hourlyRate int
}

func (fb FixedBilling) calculate() int  {
    return fb.biddedAmount
}

func (fb FixedBilling) source() string {
    return fb.projectName
}

func (tm TimeAndMaterial) calculate() int{
    return tm.noOfHours * tm.hourlyRate
}

func (tm TimeAndMaterial) source() string {
    return tm.projectName
}

func calculateNetIncome(ic []Income)  {
    var count int = 0
    for _, v := range ic{
        fmt.Println(v.calculate())
        fmt.Println(v.source())
        count += v.calculate()
    }
    fmt.Println(count)
}

func main()  {
    pro1 := FixedBilling{"allen", 12000}
    pro2 := FixedBilling{"mary", 7000}
    pro3 := TimeAndMaterial{"tom", 12, 80}
    fmt.Println(pro1, pro2, pro3)
    ic := []Income{pro1, pro2, pro3}
    calculateNetIncome(ic)
}

上邊代碼中,FixedBilling 結構體和 TimeAndMaterial 都實現了接口中的兩個方法,所以在calculateNetIncome函數中,可以通用通過v.calculate()調用不同結構體的calculate方法,這就是簡單的多態。

參考 https://studygolang.com/articles/12681

Defer

defer 語句的用途是:含有 defer 語句的函數,會在該函數將要返回之前,調用另一個函數。
defer 不僅限於函數的調用,調用方法也是合法的。

示例:

package main

import (  
    "fmt"
)

func finished() {  
    fmt.Println("Finished finding largest")
}

func largest(nums []int) {  
    defer finished()
    fmt.Println("Started finding largest")
    max := nums[0]
    for _, v := range nums {
        if v > max {
            max = v
        }
    }
    fmt.Println("Largest number in", nums, "is", max)
}

func main() {  
    nums := []int{78, 109, 2, 563, 300}
    largest(nums)
}

輸出:
Started finding largest  
Largest number in [78 109 2 563 300] is 563  
Finished finding largest

延遲方法

package main

import (  
    "fmt"
)


type person struct {  
    firstName string
    lastName string
}

func (p person) fullName() {  
    fmt.Printf("%s %s",p.firstName,p.lastName)
}

func main() {  
    p := person {
        firstName: "John",
        lastName: "Smith",
    }
    defer p.fullName()
    fmt.Printf("Welcome ")  
}

運行結果:
Welcome John Smith
延遲函數實參取值(Arguments Evaluation)

在 Go 語言中,並非在調用延遲函數的時候才確定實參,而是當執行 defer 語句的時候,就會對延遲函數的實參進行求值。

package main

import (  
    "fmt"
)

func printA(a int) {  
    fmt.Println("value of a in deferred function", a)
}
func main() {  
    a := 5
    defer printA(a)
    a = 10
    fmt.Println("value of a before deferred function call", a)

}

可以看到結果,a 變量值仍然爲 5。
defer 棧

當一個函數內多次調用 defer 時,Go 會把 defer 調用放入到一個棧中,隨後按照後進先出(Last In First Out, LIFO)的順序執行。

func demo1()  {
    fmt.Println("allen")
}

func demo2()  {
    fmt.Println("is")
}

func demo3()  {
    fmt.Println("student")
}

func main()  {
    defer demo3()
    defer demo2()
    defer demo1()
    fmt.Println("end")
}

輸出結果:
end
allen
is
student

再比如:字符串逆序輸出

func main() {
    name := "Naveen"
    fmt.Printf("Orignal String: %s\n",  name)
    fmt.Printf("Reversed String: ")
    for _, v := range []rune(name) {
        defer fmt.Printf("%c", v)
    }
}
defer 的實際應用
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章