文章目錄
- Struct定義
- struct 方法
- 工廠模式
- 面向對象三大特性
-
- 封裝
- 繼承
- 接口
- 多态
- Golang支援面向對象程式設計,但是和傳統面向對象有差別,并不是純粹面向對象的語言,隻能說Golang支援面向對象程式設計特性
- Golang沒有類,通過結構體struct 來實作OOP,非常簡潔。
Struct定義
- Struct字段聲明文法同變量,建立一個結構體後如果沒有為字段指派 則字段取值為預設值
- 結構體可以進行type 重新定義,Golang預設是新類型
- 結構體是使用者單獨定義的類型,和其他類型轉換時需要有完全相同的字段(名字、個數、類型)
- struct 每個字段 都可以寫上一個tag,該tag 可以通過反射機制擷取
- 結構體所有字段在記憶體中是連續的
示例代碼
//聲明一個結構體
type Cat struct{
Name string
Age int
Color string
Hobby string
}
func test01(){
//建立結構體并指派1
var cat1 Cat
cat1.Name = "小白"
cat1.Age = 10
cat1.Color = "白色"
cat1.Hobby = "吃 魚"
var cat2 Cat
fmt.Println(cat1,cat2)//{小白 10 白色 吃 魚} { 0 }
//建立結構體并指派2
c3 := Cat{"小黑",20,"黑色","shuijiao"}
//建立結構體并指派3
var c4 *Cat = new(Cat)
(*c4).Name = "小花" //通過索引通路值 再通路屬性
c4.Age = 10 //也可以直接通過索引通路屬性(goLang做了優化)
//建立結構體并指派4
var c5 *Cat = &Cat{}
c5.Name = "c5"
fmt.Println(cat1,cat2,c3,c4,c5)
c6 := c3
c6.Name ="c6"
//指派後會直接克隆一個變量給C6,修改c6的值 c3 不受影響
//{小黑 20 黑色 shuijiao} {c6 20 黑色 shuijiao}
fmt.Println(c3,c6)
//建立結構體變量時直接指定字段值
var c7 = Cat{
Name:"小綠",
Age:40,
}
fmt.Println(c7)
}
type Point struct{
x int
y int
}
type Rect struct{
leftUp, rightDown Point
}
type Rect2 struct{
leftUp, rightDown *Point
}
func test02(){
//結構體所有字段在記憶體中是連續的
r1 := Rect{Point{1,2} , Point{3,4}}
fmt.Printf("r1.rd.x位址= %p r1.rd.y位址= %p r1.lu.x位址= %p r1.lu.y位址= %p \n",
&r1.rightDown.x,&r1.rightDown.y,&r1.leftUp.x,&r1.leftUp.y)
// fmt.Printf("r1.rd.x位址= %p r1.rd.y位址= %p r1.lu.x位址= %p r1.lu.y位址= %p \n",
//&r1.rightDown.x,&r1.rightDown.y,&r1.leftUp.x,&r1.leftUp.y)
r2 := Rect2{&Point{1,2} , &Point{3,4}}
fmt.Printf("r2.rd.x位址= %p r2.rd.y位址= %p r2.lu.x位址= %p r2.lu.y位址= %p \n",
&r2.rightDown.x,&r2.rightDown.y,&r2.leftUp.x,&r2.leftUp.y)
//結構體是使用者單獨定義的類型,和其他類型轉換時需要有完全相同的字段(名字、個數、類型)
type A struct{
Num int
}
type B struct{
Num int
}
var a A
var b B
a = A(b)
a.Num =10
fmt.Println(a,b)
//結構體進行type 重新定義,Golang預設是新類型,
type AA A
var aa AA
//var a A = aa 報錯需要強轉
var a3 A = A(aa)
fmt.Println(aa,a3)
//struct 每個字段 都可以寫上一個tag,該tag 可以通過反射機制擷取
//常見的場景就是 序列化和反序列化
type Monster struct {
Name string `json:"name"`
Age int `json:age`
}
m1 := Monster{"牛魔王",300}
jsonstr ,err := json.marshal(m1)
if err != nil {
fmt.Println("json 字元串處理錯誤")
}
fmt.Println("json 字元串=",jsonstr)
}
struct 方法
struct 方法聲明文法 表示 A結構體有一個test 方法 (a A)辨別這個方式是和 A類型綁定的
func( a A)test(){}
func( a A)test2(參數清單)(傳回值清單){
方法體
return 傳回值
}
注意點
- 在通過一個結構變量去調用方法時,調用機制和函數相同,隻不過調用結構體方法時,結構體變量也會作為一個參數傳遞到方法中。
- Golang中的方法是指定在資料類型上的,是以所有自定義類型都可以有方法,包括 struct、int 、float32
- 如果一個類型實作了 String() 這個方法,那麼 fmt.Println 預設會調用變量的這個方法作為結果進行輸出
- 通過變量去調用方法時,其(參數傳遞)機制和函數一樣,不一樣的地方是變量本身也會作為參數傳遞給該方法(如果變量是值類型則進行值拷貝,如果是引用類型則進行地質拷貝,如果希望在方法中修改結構體的值可以使用指針來傳遞)。
- 方法的通路範圍控制和函數一樣,方法名首字母小寫隻在本包通路,大寫可以在包外通路。
示例代碼
//定義結構體
type Person struct{
Name string
}
//定義方法
func (p Person) speak(){
fmt.Println(p.Name,"是一個好人")
}
func test03(){
p := Person{"zhangsan"}
p.speak()
}
工廠模式
工廠模式用來通過指定的方法來建立(隻在包内可用的/首字母小寫的結構體)執行個體。
同時如果結構體的屬性 首字母小寫也可以通過方法類擷取(類似于java中的set/get 方法)
執行個體代碼
func test04(){
var stu = mode.NewStudent("tom",12.0)
fmt.Println(*stu)
fmt.Println(stu.Name,stu.GetScore())
}
package model
type student struct{
Name string
score float64
}
//通過工廠方法擷取 隐藏結構的執行個體
func NewStudent(n string,s float64) *student{
return &student{Name:n,score:s}
}
//通過方法或去被封裝的屬性
func( stu *student) GetScore () float64{
return stu.score
}
面向對象三大特性
Golang也有面向對象程式設計的三大特性,隻不過實作方式和其他OOP語言不同
封裝
将抽象出來的字段封裝在一起
好處:隐藏實作細節 同時借助統一通路方法,可以對資料進行驗證,保證資料合理性
實作方式
- 将結構體、字段首字母小寫(類似java private)
- 為封裝的結構體提供工廠模式函數,首字母大寫,類似一個構造函數
-
為結構體提供首字母大寫的Set/Get 方法 用于屬性讀寫
示例代碼
func test05(){
var per = mode.NewPerson("TOM")
per.SetAge(50)
per.SetSal(10000.0)
fmt.Println(per)
fmt.Println(per.GetAge(),per.GetSal(),per.Name)
}
package model
import (
"fmt"
)
type person struct{
Name string
age int
sal float64
}
// 封裝工廠方法 建立結構體執行個體
func NewPerson(name string) *person{
return &person{
Name:name,
}
}
func (p *person)SetAge(age int){
if age>0 && age<150{
p.age = age
} else{
fmt.Println("年齡不在合法範圍内")
}
}
func (p *person)SetSal(sal float64){
if sal>3000 && sal<30000{
p.sal = sal
} else{
fmt.Println("薪資不在合法範圍内")
}
}
func (p *person)GetAge() int {
fmt.Println("====>",p.age)
return p.age
}
func (p *person)GetSal() float64 {
fmt.Println("====>",p.sal)
return p.sal
}
繼承
繼承可以解決代碼的複用,當多個結構體存在相同的字段和方法時,可以抽象出一個結構體包含公有的屬性和方法。其他結構體不用定義這些屬性和方法,隻需要嵌套這個結構體即可。
就是說如果一個結構體嵌套了另外一個匿名結構體,那麼這個結構體可以直接通路匿名結構體的方法和字段,如此就實作了繼承。
基本文法形式
type Goods struct{
Name string
Price int
}
type Book struct{
Goods //嵌套匿名結構體
Writer string
}
示例代碼
func test06(){
pu := &Pupil{}
pu.Student.Name = "TOM"
pu.Student.Age =12
pu.Age=13//對繼承的字段可以簡化通路
pu.testing();
pu.Student.ShowInfo()
pu.ShowInfo()//方法簡化通路
//聲明結構體時直接為嵌套結構體指派
pu2 := &Pupil{
Student{Name: "Jack",
Age:19,
Score:100,
},
}
pu2.ShowInfo()
}
//定義學生類
type Student struct{
Name string
Age int
Score int
}
//顯示學生資訊
func (stu *Student)ShowInfo(){
fmt.Println(stu)
}
func (stu *Student) SetCore(score int){
stu.Score = score
}
//定義國小生類
type Pupil struct{
Student//嵌入 學生結構體
}
func (p *Pupil) testing(){
fmt.Println("國小生正在考試...")
}
注意點
- 結構體可以使用匿名結構體的所有字段和方法,無論首字母大寫還是小寫
- 結構體通路匿名結構體方法和屬性可以簡化,簡化通路時通路邏輯:如果目前結構體有通路屬性/方法則直接調用本結構體的該屬性/方法,如果目前結構體沒有該屬性/方法,則到嵌套的匿名結構體中找該屬性/方法,找到就調用,如果找不到就報錯。
- 當結構體和嵌套結構體有相同的屬性或方法時,編譯器采用就近通路原則。如果想調用匿名結構體中的屬性/方法這個時候就必須 通過匿名結構體的名字進行調用(不能進行簡化調用)
- 如果一個結構體嵌套了多個結構體,且多個嵌套結構體包含相同的屬性/方法,那麼在調用時就必須指明匿名結構體的名字。否則編譯器報錯
- 如果一個結構體嵌套了一個有名字的結構體,那麼二者是組合關系,這個時候通路組合結構體的屬性/方法時必須帶上 結構體的名字
- 嵌套結構體變量後,可以在建立結構體變量時直接為嵌套結構體指派。
- 如果一個結構體中嵌套了基本資料類型如int,通路方式 A.int=12, 如果要有多個int字段必須制定名字
多重繼承
如果一個struct 嵌套了多個匿名結構體,那麼它可以通路所有結構體的字段和方法,這就是Golang的多重繼承
*如果嵌套的多個結構體中包含相同的屬性/方法,那麼在調用方法時必須明确嵌套結構體的名字
接口
interface 類型可以定義一組方法,但是不需要實作,并且interface 不能包含任何變量,在具體類型中根據實際情況實作這些方法。
type 接口名 interface{
method1()
method2()
}
示例程式
type Gun interface{
Fire()
}
type Gun51 struct{
}
func(g Gun51) Fire(){
fmt.Println("Gun51連續發射7mm子彈...")
}
type GunRPG struct{
}
func(g GunRPG) Fire(){
fmt.Println("RPC發射火箭彈...")
}
func test07(){
var g51 Gun51
var rpg GunRPG
var gun Gun = g51
gun.Fire();
gun = rpg
gun.Fire()
}
注意點
- 接口裡所有的方法都沒有方法體,展現了程式 高内聚低耦合的思想
- Golang 中不顯式的實作接口,隻要一個變量中包含接口聲明的所有方法,就認為這個變量實作了這個接口。
- 接口本身不建立執行個體,但是可以指向一個實作了該接口的執行個體
- 一個自定義類型隻有實作了某個接口,才可以将該類型變量指派給這個接口。
- 任何自定義類型都可以實作接口 不一定是結構體,且一個變量可以實作多個接口
- 接口A可以繼承多個其他接口B、C,這個時候如果變量實作A接口也必須同時實作B和C接口。如果接口(包括繼承的接口)中包含重複的方法,那麼編譯器報錯
- interface 預設是一個指針類型,如果沒有初始化預設輸出nil
- 空接口 沒有任何方法,所有類型都實作了空接口,即所有類型都可以指派給空接口
繼承和接口比較
繼承主要解決代碼的複用性和可維護性。強調共性複用。
接口主要是設計規範,讓其他類型遵守規範 。強調共性能力 各自實作。
接口一定程度上實作代碼解耦
多态
變量具有多種形态,Golang中多态通過接口實作。按照同一個接口在不同變量中做不同實作,這時接口就呈現多種不同狀态。(見接口示例)
一般通過方法參數展現多态
類型斷言
如何将一個接口變量指派給自定義類型變量?需要先确定變量的類型,即類型斷言
示例代碼
func test08(){
var x interface{}
var b2 int = 1
x = b2
// 将x 轉換為int
y, ok := x.(int);
if ok {
fmt.Printf("轉換成功 y的類型是%T 值是=%v \n",y,y);
}else{
fmt.Println("轉換失敗")
}
fmt.Println("x.(int)==>",x.(int))
//fmt.Println("x.(type)==>",x.(type)) 隻能在switch 中使用 這裡使用報錯
//調用斷言方法
TypeJudge(b2)
}
func TypeJudge(items... interface{}){
for index,x := range items{
switch x.(type){
case bool :
fmt.Printf("第%v個參數是 bool 類型,值是 %v \n",index,x)
case float32:
fmt.Printf("第%v個參數是 float32 類型,值是 %v \n",index,x)
case float64:
fmt.Printf("第%v個參數是 float64 類型,值是 %v \n",index,x)
case int,int32,int64:
fmt.Printf("第%v個參數是 int 類型,值是 %v \n",index,x)
case string:
fmt.Printf("第%v個參數是 string 類型,值是 %v \n",index,x)
default:
fmt.Printf("第%v個參數 類型不确定,值是 %v \n",index,x)
}
}
}