面向對象編程
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)
}
}