Go語言基礎之面向物件程式設計中
1 Golang面向物件程式設計基本介紹
Golang仍然有面向物件程式設計的繼承,封裝和多型的特性,只是實現的方式和其它OPP語言不一樣,隨後分別介紹Golang對面向物件程式設計的三大特性是如何實現的。
2 面向物件程式設計-封裝
2.1 封裝介紹
封裝(encapsulation)就是把抽象出的欄位和對欄位的操作封裝在一起,資料被保護在內部,程式的其它包只有通過被授權的操作(方法)才能對欄位進行操作。
2.2 封裝的作用
用通俗的電視機來理解封裝:電視機是由不同的零件組裝在一起,具有將接收的訊號顯示在螢幕上功能;作為家電,其向用戶展現的是對應的功能,使用者不用考慮其內部是怎麼實現,只要充分利用電視機所提供的功能即可。那麼類比到程式中這樣做有什麼好處:
隱藏實現細節;
可以對資料進行驗證,保證資料的安全。
2.3 Golang中的封裝體現
對結構體中的屬性進行封裝
通過方法和包實現封裝
2.4 封裝實現步驟
Golang中封裝實現步驟:
將結構體、欄位(屬性)的首字母小寫(其它包不能使用,類似private);
給結構體所在包提供一個工廠模式的函式(建構函式),首字母大小;
提供一個首字母大寫的
Set
方法(類似其他語言的public),用於對屬性判斷並賦值 ,Set
方法的結構如下:
func (var 結構體型別名) SetXxx(引數列表) (返回值列表) { //加入資料驗證的業務邏輯 var.欄位 = 引數 }
- 提供一個首字母大寫的
Get
方法(類似其他語言的public),用於獲取屬性值,Get
方法的結構如下:
func (var 結構體型別名) GetXxx(引數列表) (返回值列表) {
return var.欄位
}
另外,在Golang開發中並沒有特別強調封裝,Golang本身對面向物件的特性做了簡化。
2.5 封裝案例
需求:設計一個person
結構體,不能隨便檢視person
的年齡、工資等隱私,並對輸入的年齡進行合理的驗證。
設計:model包(person.go
實現person
結構體),main包(main.go
呼叫peron
結構體)。
程式碼實現:
//model/person.go package model //定義一個person的結構體 type person struct { Name string age int sala float64 } //寫一個工廠模式函式,類似建構函式 func NewPerson(name string) *person { return &person{ Name : name } } //為了訪問age 和 sala,以age欄位為例編寫對應的SetXxx和GetXxx方法 func (p *person) SetAge(age int) { if age > 0 && age < 150 { p.age = age } else { fmt.Println("年齡範圍不正確...") } } func (p *person) GetAge() int { return p.age } //main/main.go 匯入model包 func main() { p := model.NewPerson("tom") p.SetAge(20) fmt.Println(p) fmt.Pritnln(p.Name, "age=", p.GetAge()) }
3 面向物件程式設計-繼承
3.1 繼承介紹
在現實中,一些事物有其共性,比如大學生、小學生都具有學生共有的屬性和方法,那麼我們可以將這些共同的屬性和方法抽象出結構體並讓大學生和小學生繼承這些屬性和方法,這樣可以減少一些重複定義重複使用的工作。
繼承可以提高程式碼複用、擴充套件性和維護性,讓程式設計更加靠近人類思維。當多個結構體存在相同的屬性和方式時,可以從這些結構體中抽象出結構體,在該結構體中定義這些相同的屬性和方法。對於其他結構體不需要重新定義這些屬性和方法,只需巢狀一個匿名結構體即可。以學生、大學生以及小學生為例,示意圖如下:
在Golang中,如果一個struct
嵌套了另一個匿名結構體,那麼這個結構體可以直接訪問匿名結構體的欄位和方法,從而實現了繼承特性。
3.2 巢狀匿名結構體的基本語法
//以書籍繼承貨物為例
type Goods struct {
Name string
Price float64
}
type Book struct {
Goods //巢狀匿名結構體Goods
Writer string
}
3.3 繼承細節及注意事項
結構體可以使用巢狀匿名結構體所有的欄位和方法,即:首字母大寫或小寫的欄位、方法都可以使用,示例如下:
type A struct {
Name string
age int
}
func (a *A) SayOk() {
fmt.Println("A SayOk", a.Name)
}
func (a *A) hello() {
fmt.Println("A hello", a.Name)
}
type B struct {
A
}
func main() {
var b B
b.A.Name = "tom"
b.A.SayOk()
b.A.hello()
}
匿名結構體欄位訪問可以簡化:
func main() {
var b B
b.A.Name = "tom"
b.A.SayOk()
b.A.hello()
b.Name = "viktor"
b.SayOk()
b.hello()
}
/*
當直接通過b訪問欄位或方法時,比如b.Name:
編譯器會先看b對應的型別有沒有Name,如果有,則直接呼叫B型別的Name欄位;
如果沒有就去找B中巢狀的匿名結構體A有沒有宣告Name欄位,如果有就呼叫;
沒有的話繼續查詢,如果都找不到就報錯
*/
當結構體和匿名結構體有相同的欄位或者方法時,編譯器採用就近訪問原則訪問,如果希望訪問匿名結構體的欄位和方法,可以通過匿名結構體名來區分;
結構體嵌入兩個(或多個)匿名結構體,如兩個匿名結構體有相同的欄位和方法(同時結構體本身沒有同名的欄位和方法),在訪問時,就必須明確指定匿名結構體名字,否則編譯報錯;
type A struct {
Name string
age int
}
type B struct {
Name string
Score float64
}
type C struct {
A
B
}
func main() {
var c C
//c.Name = "tom" //報錯
c.A.Name = "tom"
fmt.Println(c)
}
如果一個struct
嵌套了一個有名結構體,這種模式就是組合,如果是組和關係,那麼在訪問組和的結構體的欄位或方法時,必須帶上結構體的名字;
type D struct {
a A
}
func main() {
var d D
d.a.Name = "viktor"
}
巢狀匿名結構體後,也可以在建立結構體變數時,直接指定各個匿名結構體欄位的值;
type Goods struct {
Name string
Price float64
}
type Brand struct {
Name string
Address string
}
type TV struct {
Goods
Brand
}
type TV2 struct {
*Goods
*Brand
}
func main() {
tv := TV{Goods{"001", 5000.88}, Brand{"長虹","四川"},}
tv2 := TV{
Goods{
Price : 5000,
Name : "002",
},
Brand{
Name : "海爾"
Address : "山東"
},
}
fmt.Println("tv", tv)
fmt.Println("tv2", tv2)
tv3 := TV{&Goods{"003", 5000.88}, &Brand{"創維","河南"},}
tv4 := TV{
&Goods{
Price : 5000,
Name : "004",
},
&Brand{
Name : "夏普"
Address : "北京"
},
}
fmt.Println("tv3", *tv3.Goods, *tv3.Brand)
fmt.Println("tv4", *tv4.Goods, *tv4.Brand)
}
3.4 多重繼承
如果一個struct
嵌套了多個匿名結構體,那麼該結構體可以直接訪問巢狀的匿名結構體的欄位和方法,從而實現了多重繼承。
在3.3中最後一個示例中,TV
就是一個多重繼承的結構體,繼承了Goods
,Brand
兩個結構體。
多重繼承注意事項:
- 如果嵌入的匿名結構體有相同的欄位名或者方法名,則在訪問時,需要通過匿名結構體型別名來區分;
- 為了保證程式碼的簡潔性,建議儘量不要使用多重繼承。
4練習
使用面向物件的思維方式編寫一個學生資訊管理系統:
- 學生有id、姓名、年齡、分數等資訊;
- 程式提供展示學生列表、新增學生、編輯學生資訊、刪除學生等功能。