文章目錄
- 封裝
- 基本介紹
- 封裝的實現
- 工廠函數
- 繼承
- 基本介紹
- 繼承的實現
- 字段和方法訪問細節
- 多繼承
封裝
基本介紹
基本介紹
- 封裝(Encapsulation)是面向對象編程(OOP)中的一種重要概念,封裝通過將數據和相關的方法組合在一起,形成一個稱為類的抽象數據類型,只暴露必要的接口供外部使用。
- 封裝可以隱藏數據的實際實現細節,外部只能通過公共(public)接口來訪問和修改數據,使得代碼更加模塊化和結構化,同時可以防止不恰當的訪問和操作,提高數據的安全性。
- 封裝將相關的數據和方法組織在一起,形成了一個獨立的單元,外部使用者只需關心公共接口,無需了解內部實現細節,簡化了使用方式,提高了代碼的可讀性和可維護性。
- 封裝使得內部實現可以獨立于外部接口進行修改,如果內部實現發生了變化,只需要確保公共接口的兼容性,而不會影響使用該類的其他代碼,提供了更好的靈活性和可擴展性。
封裝的實現
封裝的實現
- Go中的封裝是通過命名約定和訪問控制來實現的,而不像一些其他面向對象語言那樣使用訪問修飾符(如public、private、protected),因此開發者需要自覺遵守約定來保持封裝的效果。
- Go中通過結構體將相關的字段和方法組合在一起,并通過首字母大小寫來控制其可訪問性。結構體中的字段和方法使用大寫字母開頭表示公共的(可導出的),可以被其他包訪問,而使用小寫字母開頭表示私有的(不可導出的),只能在當前包內使用。
- Go中的封裝更加寬泛,其封裝的基本單元不是結構體而是包(package),包內的所有標識符(變量、函數、結構體、方法等)都通過首字母大小寫來控制其可訪問性
封裝案例如下:
package modelimport "fmt"type Student struct {name stringage intgender string
}// 訪問name字段
func (stu Student) GetName() string {return stu.name
}
func (stu *Student) SetName(name string) {stu.name = name
}// 訪問age字段
func (stu Student) GetAge() int {return stu.age
}
func (stu *Student) SetAge(age int) {stu.age = age
}// 訪問gender字段
func (stu Student) GetGender() string {return stu.gender
}
func (stu *Student) SetGender(gender string) {stu.gender = gender
}// Student的其他方法
func (stu Student) Print() {fmt.Printf("student info: <name: %s, age: %d, gender: %s>\n",stu.name, stu.age, stu.gender)
}
使用上述包內結構體的案例如下:
package mainimport ("go_code/OOP2/Encapsulate/model"
)func main() {// var stu = model.Student{"Alice", 12, "女"} // 隱式賦值var stu model.Studentstu.SetName("Alice")stu.SetAge(12)stu.SetGender("女")stu.Print() // student info: <name: Alice, age: 12, gender: 女>
}
注意: Go中無法對結構體中不可導出的字段進行隱式賦值,可以通過給結構體綁定對應的setter和getter方法對不可導出的字段進行訪問和賦值。
工廠函數
工廠函數
如果結構體類型的首字母小寫(不可導出),那么其他包將無法直接使用該結構體類型來創建實例,這時可以在包內提供對應可導出的工廠函數來創建實例并返回。如下:
package modelimport "fmt"type student struct {name stringage intgender string
}// 工廠函數
func NewStudent(name string, age int, gender string) *student {return &student{name: name,age: age,gender: gender,}
}func (stu student) Print() {fmt.Printf("student info: <name: %s, age: %d, gender: %s>\n",stu.name, stu.age, stu.gender)
}
其他包通過調用包中可導出的工廠函數,即可創建對應不可導出的結構體實例。如下:
package mainimport ("go_code/OOP2/Encapsulate/model"
)func main() {var stu = model.NewStudent("Alice", 12, "女")stu.Print() // student info: <name: Alice, age: 12, gender: 女>
}
繼承
基本介紹
基本介紹
- 繼承是面向對象編程中的一個重要概念,其允許一個類(子類/派生類)繼承另一個類(父類/基類)的屬性和方法,子類繼承父類后可以直接訪問和使用父類中字段和方法,同時可以添加自己的字段和方法。
- 繼承的主要優勢在于代碼復用,繼承可以在不重復編寫相似代碼的情況下擴展現有的類,使代碼更具可維護性和可擴展性。
繼承示意圖如下:
繼承的實現
繼承的實現
- Go中的繼承是通過嵌套匿名結構體的方式來實現的,如果一個結構體中嵌套了另一個匿名結構體,那么這個結構體可以直接訪問匿名結構體中的字段和方法,從而實現了繼承的效果。
繼承案例如下:
package mainimport ("fmt"
)// 基類
type Person struct {Name stringAge int
}
func (per Person) PrintInfo() {fmt.Printf("name = %s, age = %d\n", per.Name, per.Age)
}// 派生類
type Student struct {Person // 嵌套匿名結構體StudentId int
}
func (stu Student) Study() {fmt.Printf("student %d is studying...\n", stu.StudentId)
}// 派生類
type Teacher struct {*Person // 嵌套匿名結構體指針TeacherId int
}
func (tch Teacher) Teach() {fmt.Printf("teacher %d is teaching...\n", tch.TeacherId)
}func main() {var stu = Student{Person{"Alice", 12}, 100}stu.PrintInfo() // name = Alice, age = 12stu.Study() // student 100 is studying...var tch = Teacher{&Person{"Bob", 22}, 200}tch.PrintInfo() // name = Bob, age = 22tch.Teach() // teacher 200 is teaching...
}
說明一下:
- 在嵌套匿名結構體時,可以通過
Type
的方式嵌套匿名結構體,也可以通過*Type
的方式嵌套匿名結構體指針。 - 在創建結構體變量時,如果要通過字段名的方式初始化結構體字段,那么匿名結構體的字段名由匿名結構體的類型名充當。
- 在結構體中嵌套匿名結構體后,可以通過結構體實例訪問匿名結構體的字段和方法,但在訪問時仍然遵循Go的命名約定和訪問控制。如果被嵌套的匿名結構體的定義在其他包,那么通過結構體實例只能訪問匿名結構體可導出的字段和方法。
- 結構體中嵌入的匿名字段也可以是基本數據類型,在訪問結構體中的匿名基本數據類型字段時,以對應基本數據類型的類型名作為其字段名。比如結構體中嵌入了一個匿名int字段,則通過
結構體變量名.int
的方式對其進行訪問。
組合
在結構體中嵌套有名結構體屬于組合關系,在訪問組合的結構體字段和方法時,必須帶上結構體的字段名。如下:
package mainimport ("fmt"
)// 車輪
type Wheel struct {Color stringprice int
}// 自行車
type Bicycle struct {FrontWheel Wheel // 組合RearWheel Wheel // 組合// ...
}
func (bc Bicycle) Print() {fmt.Printf("前輪<color:%s, price:%d元> 后輪<color:%s, price:%d元>\n",bc.FrontWheel.Color, bc.FrontWheel.price, bc.RearWheel.Color, bc.RearWheel.price)
}func main() {var bc = Bicycle{FrontWheel: Wheel{"black", 100},RearWheel: Wheel{"blue", 200},}bc.Print() // 前輪<color:black, price:100元> 后輪<color:blue, price:200元>
}
字段和方法訪問細節
字段和方法訪問細節
結構體字段和方法的訪問流程:
- 先查看當前結構體類型中是否有對應的字段或方法,如果有則訪問。
- 再查看結構體中嵌入的匿名結構體中是否有對應的字段或方法,如果有則訪問。
- 繼續查找更深層次嵌入的匿名結構體中是否有對應的字段或方法,如果有則訪問,否則產生報錯。
案例如下:
package mainimport ("fmt"
)// 基類
type Person struct {Name stringAge int
}
func (per Person) PrintInfo() {fmt.Printf("name = %s, age = %d\n", per.Name, per.Age)
}// 派生類
type Student struct {PersonStudentId int
}func (stu Student) Study() {fmt.Printf("student %d is studying...\n", stu.StudentId)
}
func (stu Student) PrintInfo() {fmt.Printf("name = %s, age = %d, id = %d\n", stu.Name, stu.Age, stu.StudentId)
}func main() {var stu = Student{Person{"Alice", 12}, 100}fmt.Printf("name = %s\n", stu.Name) // name = Alicestu.PrintInfo() // name = Alice, age = 12, id = 100
}
代碼說明:
- 在訪問字段時,由于Student結構體中沒有Name字段,因此
Student變量.Name
訪問的是嵌套的匿名結構體Person中的Name字段。 - 在訪問方法時,由于Student結構體有PrintInfo方法,因此
Student變量.PrintInfo()
訪問的是Student結構體的PrintInfo方法,而不是Person結構體的PrintInfo方法。
多繼承
多繼承
多繼承指的是一個結構體中嵌套了多個匿名結構體,如果嵌套的多個匿名結構體中有相同的字段名或方法名,那么在訪問時需要通過匿名結構體的類型名進行指明訪問。如下:
package mainimport ("fmt"
)type Cat struct {Name stringAge int
}type Dog struct {Name stringAge int
}// 多繼承
type Pet struct {CatDog
}func main() {var pet = Pet{Cat: Cat{Name: "小貓",Age: 2,},Dog: Dog{Name: "小狗",Age: 3,},}fmt.Printf("cat name = %s\n", pet.Cat.Name) // cat name = 小貓fmt.Printf("dog name = %s\n", pet.Dog.Name) // dog name = 小狗
}