Golang仍然有面向對象程式設計的繼承,封裝和多态的特性,隻是實作的方式和其它OPP語言不一樣。
1 Golang面向對象程式設計基本介紹
Golang仍然有面向對象程式設計的繼承,封裝和多态的特性,隻是實作的方式和其它OPP語言不一樣,随後分别介紹Golang對面向對象程式設計的三大特性是如何實作的。
2 面向對象程式設計-封裝
2.1 封裝介紹
封裝(encapsulation)就是把抽象出的字段和對字段的操作封裝在一起,資料被保護在内部,程式的其它包隻有通過被授權的操作(方法)才能對字段進行操作。
2.2 封裝的作用
用通俗的電視機來了解封裝:電視機是由不同的零件組裝在一起,具有将接收的信号顯示在螢幕上功能;作為家電,其向使用者展現的是對應的功能,使用者不用考慮其内部是怎麼實作,隻要充分利用電視機所提供的功能即可。那麼類比到程式中這樣做有什麼好處:
- 隐藏實作細節;
- 可以對資料進行驗證,保證資料的安全。
2.3 Golang中的封裝展現
- 對結構體中的屬性進行封裝
- 通過方法和包實作封裝
2.4 封裝實作步驟
Golang中封裝實作步驟:
- 将結構體、字段(屬性)的首字母小寫(其它包不能使用,類似private);
- 給結構體所在包提供一個工廠模式的函數(構造函數),首字母大小;
- 提供一個首字母大寫的
方法(類似其他語言的public),用于對屬性判斷并指派 ,Set
方法的結構如下:Set
func (var 結構體類型名) SetXxx(參數清單) (傳回值清單) {
//加入資料驗證的業務邏輯
var.字段 = 參數
}
- 提供一個首字母大寫的
方法(類似其他語言的public),用于擷取屬性值,Get
方法的結構如下: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、姓名、年齡、分數等資訊;
- 程式提供展示學生清單、添加學生、編輯學生資訊、删除學生等功能。