作者:尹正傑
版權宣告:原創作品,謝絕轉載!否則將追究法律責任。
目錄
- 一.Go語言的封裝(encapsulation)實現
- 1.什麼是封裝(encapsulation)
- 2.封裝(encapsulation)的好處
- 3.golang如何實現封裝(encapsulation)
- 4.程式碼實現
- 4.1 程式碼組織結構
- 4.2 建立go.mod檔案
- 4.3 dongman.go
- 4.4 main.go
- 二.Go語言的繼承(inheritance)實現
- 1.繼承(inheritance)概述
- 2.繼承案例程式碼實現
- 3.Golang支援多繼承
- 4.巢狀匿名結構體指標
- 5.組合模式(巢狀結構體)並非繼承
- 6.巢狀結構體的欄位名衝突
- 三.Go語言的多型(polymorphic)實現
- 1.多型概述
- 2.多型案例
- 四.結構體記憶體佈局
- 1 結構體佔用一塊連續的記憶體
- 2 空結構體
- 3 記憶體對齊[瞭解即可]
一.Go語言的封裝(encapsulation)實現
1.什麼是封裝(encapsulation)
封裝(encapsulation)就是把抽象出的欄位和對欄位的操作封裝在一起。
資料被保護在內部,程式的其他包只有透過被授權的操作方法,才能對欄位進行操作。
2.封裝(encapsulation)的好處
- 1.隱藏實現細節;
- 2.可以對資料進行校驗,保證安全合理;
3.golang如何實現封裝(encapsulation)
- 1.建議將結構體,欄位(屬性)的首字母小寫(其他包不能使用,類似private,實際開發不小寫也可能,因為封裝沒有那麼嚴格);
- 2.給結構體所在的包提供一個工程模式函式,首字母大寫(類似於一個建構函式);
- 3.提供一個首字母大寫的set方法(類似於其他語言的public),用於對屬性判斷並賦值
- 4.提供一個首字母大寫的Get方法(類似於其他語言的publiic),用於獲取屬性的值;
4.程式碼實現
4.1 程式碼組織結構
如上圖所示,程式碼組織結構
4.2 建立go.mod檔案
yinzhengjie@bogon 12-encapsulation % go mod init yinzhengjie-fengzhuang
go: creating new go.mod: module yinzhengjie-fengzhuang
yinzhengjie@bogon 12-encapsulation %
yinzhengjie@bogon 12-encapsulation % ls
go.mod
yinzhengjie@bogon 12-encapsulation %
yinzhengjie@bogon 12-encapsulation % cat go.mod
module yinzhengjie-fengzhuang
go 1.22.4
yinzhengjie@bogon 12-encapsulation %
4.3 dongman.go
package dongman
import "fmt"
type dongMan struct {
Name string
// 此處為故意將age,hobby欄位設定為小寫,這意味著其他包無法直接訪問這兩個屬性。
age int
hobby []string
Leader string
}
func (d dongMan) String() string {
return fmt.Sprintf("[%s]的男主是[%s],在[%d]歲時修煉到元嬰,我的愛好是: %s", d.Name, d.Leader, d.age, d.hobby)
}
// 定義工程模式函式,相當於其他Java和Python等程式設計的構造器
func NewDongMan(name string, age int, leader string, hobby []string) *dongMan {
return &dongMan{
Name: name,
age: age,
Leader: leader,
hobby: hobby,
}
}
// 定義set方法,對age欄位進行封裝,因為在方法中可以新增一系列的限制操作,確保被封裝欄位的安全合理性
func (d *dongMan) SetAge(age int) {
// 透過set方法,我們可以設定age的範圍,否則外部就可以直接對age欄位進行賦值
if age > 0 && age < 1000 {
d.age = age
} else {
fmt.Printf("元嬰期修士壽命範圍在0~1000歲,您傳入的[%d]不合法\n", age)
}
}
// 定義get方法,用於外部包獲取隱藏的欄位
func (d *dongMan) GetAge() int {
return d.age
}
4.4 main.go
package main
import (
"fmt"
"yinzhengjie-fengzhuang/dongman"
)
func main() {
// 建立dongMan結構體
d := dongman.NewDongMan("《凡人修仙傳》", 217, "韓立", []string{"養靈蟲", "制傀儡", "煉丹", "修煉", "陣法"})
fmt.Println(d)
// 跨包無法訪問小寫字母的屬性欄位
// d.age = 400
// d.hobby = []string{"韓跑跑", "撿破爛"}
// 由於我們將age屬性封裝起來了,想要訪問該欄位,則需要透過SetAge方法進行修改,如果欄位不合法則不會設定成功喲~
// d.SetAge(2000)
d.SetAge(300)
// 由於我們將age屬性封裝起來了,想要訪問該欄位,則需要透過GetAge方法進行檢視
fmt.Printf("我是[%s]男主[%s],今年[%d]歲~\n", d.Name, d.Leader, d.GetAge())
}
二.Go語言的繼承(inheritance)實現
1.繼承(inheritance)概述
當多個結構體存在相同的屬性(欄位)和方法時,可以從這些結構體中抽象出結構體,在該結構體中定義這些相同的屬性和方法,其他的結構體不需要重新定義這些屬性和方法,只需巢狀一個匿名結構體即可。
換句話說, 在Golang中,如果一個struct巢狀了另一個匿名結構體,那麼這個結構體可以直接訪問匿名結構體的欄位和方法,這就是所謂的繼承。
如上圖所示,繼承的優點就是提高了程式碼的複用性和擴充套件性,多個結構體無需重複定義屬性和方法,僅需關係各自結構體的方法即可。
Golang使用繼承注意事項:
- 1.結構體可以使用巢狀匿名結構體所有的欄位和方法,包括首字母大寫或者小寫的欄位,方法,都可以使用;
- 2.匿名欄位結構體欄位訪問可以簡化;
- 3.當結構體和匿名結構體有相同的欄位或者方法時,編譯器採用"就近訪問"原則訪問,如系統訪問匿名結構體的欄位和方法,可以透過匿名結構體來區分;
- 4.Golang中支援多繼承,如一個結構體巢狀了多個匿名結構體,那麼該結構體可以訪問直接巢狀的你們結構體的欄位和方法,從而實現了多重繼承;
- 5.如嵌入的匿名結構體有相同的欄位名或者方法名稱,則在訪問時,需要透過匿名結構體型別名來區分;
- 6.結構體的匿名欄位可以是基礎資料型別,呼叫時基於該基礎資料型別呼叫即可;
- 7.在建立巢狀匿名結構體變數(例項)時,可以直接指定各個匿名結構體欄位的值;
- 8.嵌入匿名結構體的指標也是可以的;
- 9.結構體的欄位可以是結構體型別的(組合模式,巢狀結構體),但這種寫法並不屬於繼承關係,只是屬於該結構體的一個欄位的型別而已;
溫馨提示:
為了保證程式碼的簡潔性,建議大家儘量不使用多重繼承,很多語言就將多重繼承去除了,但是Go中保留了。
2.繼承案例程式碼實現
package main
import "fmt"
// Animal結構體為表示動物,是其他結構體的"父結構體"
type Animal struct {
Name string
Age int
Weight float64
}
// 給Animal繫結Speark方法
func (a *Animal) Speark() {
fmt.Printf("%s又開始叫喚了...", a.Name)
}
// 給Animal繫結Show方法
func (a *Animal) Show() {
fmt.Printf("%s的今年%d歲,體重是%.2fkg\n", a.Name, a.Age, a.Weight)
}
// Bird結構體表示鳥,屬性欄位繼承自Animal
type Bird struct {
// 為了複用性,體現繼承思維,加入匿名結構體
Animal
}
// 為Bird結構體繫結特有的方法
func (b *Bird) Fight() {
fmt.Printf("快看,%s又飛起來啦~\n", b.Name)
}
// Dog結構體表示狗,屬性欄位繼承自Animal
type Dog struct {
Animal
}
// 定義Dog結構體特有的方法
func (d *Dog) Run() {
fmt.Printf("%s狗子,跑的真快\n", d.Name)
}
func (d *Dog) Jump() {
fmt.Printf("%s狗子,跳的好高\n", d.Name)
}
type Cat struct {
Animal
}
func (c *Cat) Scratch() {
fmt.Printf("%s貓咪,又開始抓人了\n", c.Name)
}
func main() {
bird := &Bird{}
bird.Animal.Name = "小黃"
bird.Animal.Age = 1
bird.Animal.Weight = 0.2
dog := &Dog{}
dog.Animal.Name = "雪花"
dog.Animal.Age = 3
dog.Animal.Weight = 4
cat := &Cat{}
cat.Animal.Name = "大花"
cat.Animal.Age = 4
cat.Animal.Weight = 2
bird.Speark()
bird.Show()
bird.Fight()
dog.Speark()
dog.Show()
dog.Run()
dog.Jump()
cat.Speark()
cat.Show()
cat.Scratch()
}
3.Golang支援多繼承
package main
import "fmt"
type Father struct {
Name string
Age int
}
type Mother struct {
Name string
}
type Son struct {
Name string
// 結構體的匿名欄位可以是基礎資料型別,這種沒有名字的欄位就稱為匿名欄位,呼叫時基於該基礎資料型別呼叫即可;
//這裡匿名欄位的說法並不代表沒有欄位名,而是預設會採用型別名作為欄位名;
// 結構體要求欄位名稱必須唯一,因此一個結構體中同種型別的匿名欄位只能有一個。
int
// 繼承多個結構體,儘管Go語言支援多繼承,但推薦少用。很多語言就將多重繼承去除了,因為容易造成邏輯混亂。
Father
Mother
}
func (f *Father) DriveCar() {
fmt.Printf("%s開車很穩~\n", f.Name)
}
func (m *Mother) Sing() {
fmt.Printf("%s唱歌很好聽~\n", m.Name)
}
func (s *Son) Dance() {
fmt.Printf("%s跳舞很好看\n", s.Name)
}
func main() {
// 構建Son結構體例項
// s := Son{"唐三", 18, Father{"唐昊", 30}, Mother{"阿銀"}}
s := Son{
"唐三",
18,
// 在建立巢狀匿名結構體變數(例項)時,可以直接指定各個匿名結構體欄位的值;
Father{
Name: "唐昊",
Age: 30,
},
Mother{
Name: "阿銀",
},
}
fmt.Printf("s = %v\n", s)
// 透過Son結構體例項的確可以呼叫多個繼承結構體的方法
s.Sing()
s.Dance()
s.DriveCar()
// 如嵌入的匿名結構體有相同的欄位名或者方法名稱,則在訪問時,需要透過匿名結構體型別名來區分;
fmt.Printf("[%s]的今年[%d]歲, 父親是: [%s], 今年[%d]歲, 母親是: [%s]\n", s.Name, s.int, s.Father.Name, s.Age, s.Mother.Name)
}
4.巢狀匿名結構體指標
package main
import "fmt"
type Father struct {
Name string
Age int
}
type Mother struct {
Name string
}
type Son struct {
Name string
int
// 嵌入匿名結構體的指標也是可以的
*Father
*Mother
}
func (f *Father) DriveCar() {
fmt.Printf("%s開車很穩~\n", f.Name)
}
func (m *Mother) Sing() {
fmt.Printf("%s唱歌很好聽~\n", m.Name)
}
func (s *Son) Dance() {
fmt.Printf("%s跳舞很好看\n", s.Name)
}
func main() {
// 構建Son結構體例項
s := Son{"唐三", 18, &Father{"唐昊", 30}, &Mother{"阿銀"}}
fmt.Printf("s = %v\n", s)
s.Sing()
s.Dance()
s.DriveCar()
fmt.Printf("[%s]的今年[%d]歲, 父親是: [%s], 今年[%d]歲, 母親是: [%s]\n", s.Name, s.int, s.Father.Name, s.Age, s.Mother.Name)
}
5.組合模式(巢狀結構體)並非繼承
package main
import (
"fmt"
)
type Address struct {
Province string
City string
}
// 一個結構體中可以巢狀包含另一個結構體或結構體指標,我們也稱之為"組合模式"。
type User struct {
Name string
Gender string
// 結構體的欄位可以是結構體型別的(組合模式),但這種寫法並不屬於繼承關係,只是屬於該結構體的一個欄位的型別而已;
Address Address
}
func main() {
user1 := User{
Name: "JasonYin",
Gender: "男",
Address: Address{
Province: "陝西",
City: "安康",
},
}
fmt.Printf("user1=%#v\n", user1)
}
6.巢狀結構體的欄位名衝突
package main
import (
"fmt"
)
// Address 地址結構體
type Address struct {
Province string
City string
CreateTime string
}
// Email 郵箱結構體
type Email struct {
Account string
CreateTime string
}
// User 使用者結構體
type User struct {
Name string
Gender string
Address
Email
}
func main() {
var u1 User
u1.Name = "Jason Yin"
u1.Gender = "男"
u1.Province = "陝西"
u1.City = "安康"
u1.Account = "y1053419035@qq.com"
// 由於2個匿名欄位都有CreateTime欄位,因此無法省略匿名欄位喲!
// u1.CreateTime = "2020"
// 巢狀結構體內部可能存在相同的欄位名。在這種情況下為了避免歧義需要透過指定具體的內嵌結構體欄位名。
u1.Address.CreateTime = "2021"
u1.Email.CreateTime = "2025"
fmt.Println(u1)
}
三.Go語言的多型(polymorphic)實現
1.多型概述
變數(例項)具有多種形態,在Go語言中,多型特徵是透過介面實現的。可以按照統一的介面來呼叫不同的介面實現。這時介面變數就呈現出不同的形態。
2.多型案例
package main
import "fmt"
// 定義SayHi介面
type SayHi interface {
SayHello()
}
type Chinese struct {
Name string
}
type American struct {
Name string
}
func (c Chinese) SayHello() {
fmt.Printf("你好,我的名字是: %s, 很高興認識你。\n", c.Name)
}
func (a American) SayHello() {
fmt.Printf("Hi,My name is %s, And you ?\n", a.Name)
}
// 定義一個函式,專門用來各國人打招呼的函式,接受具備SayHi介面能力的變數
// 此處的s(多型引數)可以透過上下文來識別具體是什麼型別的例項,此時就體現出"多型"
func greet(s SayHi) {
s.SayHello()
}
func main() {
// 定義多型陣列
var array [3]SayHi
array[0] = Chinese{"女媧"}
array[1] = American{"超人"}
array[2] = Chinese{"夸父"}
fmt.Println(array)
// 遍歷介面,呼叫介面,體現出多型的效果
for _, item := range array {
greet(item)
}
}
四.結構體記憶體佈局
1 結構體佔用一塊連續的記憶體
package main
import (
"fmt"
"unsafe"
)
// 結構體佔用一塊連續的記憶體。
type test struct {
a int8
b int8
c int8
d int8
}
func main() {
n := test{
11, 22, 33, 44,
}
fmt.Printf("n.a %p\n", &n.a)
fmt.Printf("n.b %p\n", &n.b)
fmt.Printf("n.c %p\n", &n.c)
fmt.Printf("n.d %p\n", &n.d)
// 檢視佔用的空間大小
fmt.Println(unsafe.Sizeof(n))
}
2 空結構體
package main
import (
"fmt"
"unsafe"
)
func main() {
// 空結構體是不佔用空間的。
var v struct{}
// 檢視佔用的空間大小
fmt.Println(unsafe.Sizeof(v))
}
3 記憶體對齊[瞭解即可]
關於Go語言中的記憶體對齊推薦閱讀:
https://www.liwenzhou.com/posts/Go/struct-memory-layout/