面向?qū)ο蟮娜筇卣鳎?/p>
- 封裝:隱藏對象的屬性和實現(xiàn)細節(jié),僅對外提供公共訪問方式
- 繼承:使得子類具有父類的屬性和方法或者重新定義、追加屬性和方法等
- 多態(tài):不同對象中同種行為的不同實現(xiàn)方式
Go并不是一個純面向?qū)ο蟮木幊陶Z言。在 Go 語言中可以使用結(jié)構(gòu)體struct對屬性進行封裝,結(jié)構(gòu)體就像是類的一種簡化形式??梢栽诮Y(jié)構(gòu)體上添加捆綁數(shù)據(jù)和方法的行為,這些數(shù)據(jù)和方法與類類似
Go語言沒有“類”的概念,也不支持“類”的繼承等面向?qū)ο蟮母拍睢o語言中通過結(jié)構(gòu)體的內(nèi)嵌再配合接口比面向?qū)ο缶哂懈叩臄U展性和靈活性。
結(jié)構(gòu)體和方法
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()函數(shù)
Java語言中提供了構(gòu)造方法創(chuàng)建并初始化對象,在Go語言中一般需要自己實現(xiàn)一個對外可見的New函數(shù)
func main() {
var e Employee
e.LeavesRemaining()
}
運行結(jié)果:
has 0 leaves remaining
通過運行結(jié)果可以知道,使用Employee的零值創(chuàng)建的變量是不可用的。它沒有有效的名、姓,也沒有有效的保留細節(jié)。在其他的OOP語言中,比如java,這個問題可以通過使用構(gòu)造函數(shù)來解決。使用參數(shù)化構(gòu)造函數(shù)可以創(chuàng)建一個有效的對象。
go不支持構(gòu)造函數(shù)。如果某個類型的零值不可用,則程序員的任務是不導出該類型以防止其他包的訪問,并提供一個名為NewT(parameters)的函數(shù),該函數(shù)初始化類型T和所需的值。在go中,它是一個命名一個函數(shù)的約定,它創(chuàng)建了一個T類型的值給NewT(parameters)。這就像一個構(gòu)造函數(shù)。如果包只定義了一個類型,那么它的一個約定就是將這個函數(shù)命名為New(parameters)而不是NewT(parameters)。
首先修改employee結(jié)構(gòu)體為非導出,并創(chuàng)建一個函數(shù)New(),它將創(chuàng)建一個新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))
}
在這里做了一些重要的改變。已經(jīng)將Employee struct的起始字母e設置為小寫。
由于employee是未導出的,所以不可能從其他包中創(chuàng)建類型employee的值。因此,提供了一個輸出的新函數(shù)。將所需的參數(shù)作為輸入并返回新創(chuàng)建的employee。
func main() {
e := employee.New("Sam", "Adolf", 30, 20)
e.LeavesRemaining()
}
運行結(jié)果:
Sam Adolf has 10 leaves remaining
因此,雖然Go不支持類,但是結(jié)構(gòu)體可以有效地使用,在使用構(gòu)造函數(shù)的位置,使用New(parameters)的方法即可。
組合與繼承
在 Go 語言中沒有 extends 關鍵字,它使用在結(jié)構(gòu)體中內(nèi)嵌匿名類型的方法來實現(xiàn)繼承。在Go語言中稱之為組合(Composition)。組合的一般定義是“放在一起”。
舉一個博客文章例子:每個博客都有標題、內(nèi)容和作者信息。這可以用組合完美地表示出來。
嵌入結(jié)構(gòu)體實現(xiàn)組合
可以通過將一個struct類型嵌入到另一個結(jié)構(gòu)中實現(xiàn):
/*
創(chuàng)建了一個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有字段標題、內(nèi)容。它還有一個嵌入式匿名字段作者。這個字段表示post struct是由author組成的。現(xiàn)在post struct可以訪問作者結(jié)構(gòu)的所有字段和方法。還在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()
}
運行結(jié)果:
Title: Inheritance in Go
Content: Go supports composition instead of inheritance
Author: Naveen Ramanathan
Bio: Golang Enthusiast
嵌入結(jié)構(gòu)體的切片
在以上程序的main函數(shù)下增加以下代碼,并運行
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()
}
運行結(jié)果:
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
接口與多態(tài)
Go中的多態(tài)性(Polymorphism)是在接口的幫助下實現(xiàn)的。接口可以在Go中隱式地實現(xiàn)。如果類型為接口中聲明的所有方法提供了定義,則該類型實現(xiàn)了這個接口。
任何定義接口所有方法的類型都被稱為隱式地實現(xiàn)該接口。
類型接口的變量可以保存實現(xiàn)接口的任何值。接口的這個屬性用于實現(xiàn)Go中的多態(tài)性。
定義一個正方形 Square 和一個長方形 Rectangle:
// 正方形
type Square struct {
side float32
}
// 長方形
type Rectangle struct {
length, width float32
}
計算這兩個幾何圖形的面積。但由于他們的面積計算方式不同,需要定義兩個不同的 Area() 方法。
于是,可以定義一個包含 Area() 方法的接口 Shaper,讓 Square 和 Rectangle 都實現(xiàn)這個接口里的 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() 函數(shù)中調(diào)用 Area():
func main() {
r := &Rectangle{10, 2}
q := &Square{10}
// 創(chuàng)建一個 Shaper 類型的數(shù)組
shapes := []Shaper{r, q}
// 迭代數(shù)組上的每一個元素并調(diào)用 Area() 方法
for n, _ := range shapes {
fmt.Println("圖形數(shù)據(jù): ", shapes[n])
fmt.Println("它的面積是: ", shapes[n].Area())
}
}
/*Output:
圖形數(shù)據(jù): &{10 2}
它的面積是: 20
圖形數(shù)據(jù): &{10}
它的面積是: 100
*/
由以上代碼輸出結(jié)果可知:不同對象調(diào)用 Area() 方法產(chǎn)生了不同的結(jié)果,展現(xiàn)了多態(tài)的特征。
總結(jié)
- 面向?qū)ο蟮娜筇卣魇牵悍庋b、繼承和多態(tài)
- Go 語言使用結(jié)構(gòu)體對屬性進行封裝,結(jié)構(gòu)體就像是類的一種簡化形式
- 在 Go 語言中,方法是作用在接收者(receiver)上的一個函數(shù),接收者是某種類型的變量
- 名稱首字母的大小寫決定了該變量/常量/類型/接口/結(jié)構(gòu)/函數(shù)……能否被外部包導入
- 無法被導入的字段可以使用
getter和setter的方式來訪問 - Go 語言使用在結(jié)構(gòu)體中內(nèi)嵌匿名類型的方法來實現(xiàn)繼承
- 使用接口可以實現(xiàn)多態(tài)