面向對象編程思想 抽象 抽象的介紹 我們在前面去定義一個結構體時候,實際上就是把一類事物的共有的 屬性( 欄位)和 行為( 方法)提取 出來,形成一個 物理模型(結構體)。這種研究問題的方法稱為抽象 比如一個銀行賬戶: 面向對象編程三大特性 封裝 基本介紹 Golang 仍然有面向對象編程的繼承,封 ...
面向對象編程思想-抽象
抽象的介紹
我們在前面去定義一個結構體時候,實際上就是把一類事物的共有的 屬性( 欄位)和 行為( 方法)提取
出來,形成一個 物理模型(結構體)。這種研究問題的方法稱為抽象
比如一個銀行賬戶:
package main
import (
"fmt"
)
//定義一個結構體Account
type Account struct {
AccountNo string
Pwd string
Balance float64
}
//方法
//1. 存款
func (account *Account) Deposite(money float64, pwd string) {
//看下輸入的密碼是否正確
if pwd != account.Pwd {
fmt.Println("你輸入的密碼不正確")
return
}
//看看存款金額是否正確
if money <= 0 {
fmt.Println("你輸入的金額不正確")
return
}
account.Balance += money
fmt.Println("存款成功~~")
}
//取款
func (account *Account) WithDraw(money float64, pwd string) {
//看下輸入的密碼是否正確
if pwd != account.Pwd {
fmt.Println("你輸入的密碼不正確")
return
}
//看看取款金額是否正確
if money <= 0 || money > account.Balance {
fmt.Println("你輸入的金額不正確")
return
}
account.Balance -= money
fmt.Println("取款成功~~")
}
//查詢餘額
func (account *Account) Query(pwd string) {
//看下輸入的密碼是否正確
if pwd != account.Pwd {
fmt.Println("你輸入的密碼不正確")
return
}
fmt.Printf("你的賬號為=%v 餘額=%v \n", account.AccountNo, account.Balance)
}
func main() {
//測試一把
account := Account{
AccountNo : "gs1111111",
Pwd : "666666",
Balance : 100.0,
}
//這裡可以做的更加靈活,就是讓用戶通過控制台來輸入命令...
//菜單....
account.Query("666666")
account.Deposite(200.0, "666666")
account.Query("666666")
account.WithDraw(150.0, "666666")
account.Query("666666")
}
面向對象編程三大特性-封裝
基本介紹
Golang 仍然有面向對象編程的繼承,封裝和多態的特性,只是實現的方式和其它 OOP 語言不一
樣,下麵我們一一為同學們進行詳細的講解 Golang 的三大特性是如何實現的。
封裝介紹
封裝(encapsulation)就是把抽象出的欄位和對欄位的操作封裝在一起,數據被保護在內部,程式的其
它包只有通過被授權的操作(方法),才能對欄位進行操作
封裝的理解和好處
1) 隱藏實現細節
2) 提高對 數據進行驗證,保證安全合理(Age)
如何體現封裝
1) 對結構體中的屬性進行封裝
2) 通過 方法,包 包 實現封裝
封裝的實現步驟
1) 將結構體、欄位(屬性)的首字母小寫(不能導出了,其它包不能使用,類似 private)
2) 給結構體所在包提供一個工廠模式的函數,首字母大寫。類似一個構造函數
3) 提供一個首字母大寫的 Set 方法(類似其它語言的 public),用於對屬性判斷並賦值
func (var 結構體類型名) SetXxx(參數列表) (返回值列表) {
//加入數據驗證的業務邏輯
var.欄位 = 參數
}
4) 提供一個首字母大寫的 Get 方法(類似其它語言的 public),用於獲取屬性的值
func (var 結構體類型名) GetXxx() {
return var.age;
}
特別說明:在 Golang 開發中並沒有特別強調封裝,這點並不像 Java. 所以提醒學過 java 的朋友,
不用總是用 java 的語法特性來看待 Golang, Golang 本身對面向對象的特性做了簡化的.
看一個案例
請大家看一個程式(person.go),不能隨便查看 人的年齡, 工資等隱私,並對輸入的年齡進行合理的驗
證。設計: model 包(person.go) main 包(main.go 調用 Person 結構體)
main.go
package main
import (
"fmt"
"go_code/code/chapter11/encapsulate/model"
)
func main() {
p := model.NewPerson("smith")
p.SetAge(18)
p.SetSal(5000)
fmt.Println(p)
fmt.Println(p.Name, " age =", p.GetAge(), " sal = ", p.GetSal())
}
moudel
package model
import "fmt"
type person struct {
Name string
age int //其它包不能直接訪問..
sal float64
}
//寫一個工廠模式的函數,相當於構造函數
func NewPerson(name string) *person {
return &person{
Name : name,
}
}
//為了訪問age 和 sal 我們編寫一對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
}
func (p *person) SetSal(sal float64) {
if sal >= 3000 && sal <= 30000 {
p.sal = sal
} else {
fmt.Println("薪水範圍不正確..")
}
}
func (p *person) GetSal() float64 {
return p.sal
}
面向對象編程三大特性-繼承
看一個問題,引出繼承的必要性
一個小問題,看個學生考試系統的程式 extends01.go,提出代碼復用的問題
1) Pupil 和 Graduate 兩個結構體的欄位和方法幾乎,但是我們卻寫了相同的代碼, 代碼復用性不
強
2) 出現代碼冗餘,而且代碼 不利於維護,同時 也不利於功能的擴展。
3) 解決方法-通過 繼承方式來解決
package main
import (
"fmt"
)
//編寫一個學生考試系統
type Student struct {
Name string
Age int
Score int
}
//將Pupil 和 Graduate 共有的方法也綁定到 *Student
func (stu *Student) ShowInfo() {
fmt.Printf("學生名=%v 年齡=%v 成績=%v\n", stu.Name, stu.Age, stu.Score)
}
func (stu *Student) SetScore(score int) {
//業務判斷
stu.Score = score
}
//給 *Student 增加一個方法,那麼 Pupil 和 Graduate都可以使用該方法
func (stu *Student) GetSum(n1 int, n2 int) int {
return n1 + n2
}
//小學生
type Pupil struct {
Student //嵌入了Student匿名結構體
}
//顯示他的成績
//這時Pupil結構體特有的方法,保留
func (p *Pupil) testing() {
fmt.Println("小學生正在考試中.....")
}
//大學生, 研究生。。
//大學生
type Graduate struct {
Student //嵌入了Student匿名結構體
}
//顯示他的成績
//這時Graduate結構體特有的方法,保留
func (p *Graduate) testing() {
fmt.Println("大學生正在考試中.....")
}
//代碼冗餘.. 高中生....
func main() {
//當我們對結構體嵌入了匿名結構體使用方法會發生變化
pupil := &Pupil{}
pupil.Student.Name = "tom~"
pupil.Student.Age = 8
pupil.testing()
pupil.Student.SetScore(70)
pupil.Student.ShowInfo()
fmt.Println("res=", pupil.Student.GetSum(1, 2))
graduate := &Graduate{}
graduate.Student.Name = "mary~"
graduate.Student.Age = 28
graduate.testing()
graduate.Student.SetScore(90)
graduate.Student.ShowInfo()
fmt.Println("res=", graduate.Student.GetSum(10, 20))
}
繼承可以解決代碼復用,讓我們的編程更加靠近人類思維。
當多個結構體存在相同的屬性(欄位)和方法時,可以從這些結構體中抽象出結構體(比如剛纔的
Student),在該結構體中定義這些相同的屬性和方法。
其它的結構體不需要重新定義這些屬性(欄位)和方法,只需嵌套一個 Student 匿名結構體即可
也就是說:在 Golang 中,如果一個 struct 嵌套了另一個匿名結構體,那麼這個結構體可以直接訪
問匿名結構體的欄位和方法,從而實現了繼承特性。
嵌套匿名結構體的基本語法
type Goods struct {
Name string
Price int
}
type Book struct {
Goods //這裡就是嵌套匿名結構體 Goods
Writer string
}
繼承給編程帶來的便利
1) 代碼的復用性提高了
2) 代碼的擴展性和維護性提高了
繼承的深入討論
1) 結構體可以 使用嵌套匿名結構體所有的欄位和方法,即:首字母大寫或者小寫的欄位、方法,
都可以使用。【舉例說明】
2) 匿名結構體欄位訪問可以簡化
package main
import (
"fmt"
)
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
Name string
}
func (b *B) SayOk() {
fmt.Println("B SayOk", b.Name)
}
func main() {
// var b B
// b.A.Name = "tom"
// b.A.age = 19
// b.A.SayOk()
// b.A.hello()
// //上面的寫法可以簡化
// b.Name = "smith"
// b.age = 20
// b.SayOk()
// b.hello()
var b B
b.Name = "jack" // ok
b.A.Name = "scott"
b.age = 100 //ok
b.SayOk() // B SayOk jack
b.A.SayOk() // A SayOk scott
b.hello() // A hello ? "jack" 還是 "scott"
}
對上面的代碼小結
(1) 當我們直接通過 b 訪問欄位或方法時,其執行流程如下比如 b.Name
(2) 編譯器會先看 b 對應的類型有沒有 Name, 如果有,則直接調用 B 類型的 Name 欄位
(3) 如果沒有就去看 B 中嵌入的匿名結構體 A 有沒有聲明 Name 欄位,如果有就調用,如果沒有
繼續查找..如果都找不到就報錯.
3) 當 結構體和 匿名結構體有相同的欄位或者方法時, 編譯器採用就近訪問原則訪問,如希望訪問
匿名結構體的欄位和方法,可以通過匿名結構體名來區分【舉例說明】
4) 結構體嵌入兩個(或多個)匿名結構體,如 兩個匿名結構體有相同的欄位和方法( 同時結構體本身
沒有同名的欄位和方法),在訪問時,就必須明確指定匿名結構體名字,否則編譯報錯。【舉例說明】
5) 如果一個 struct 嵌套了一個有名結構體,這種模式就是 組合,如果是組合關係,那麼在訪問組合
的結構體的欄位或方法時,必須帶上結構體的名字
6) 嵌套匿名結構體後,也可以在創建結構體變數(實例)時,直接指定各個 匿名結構體欄位的值
說明
1) 如果一個結構體有 int 類型的匿名欄位,就不能第二個。
2) 如果需要有多個 int 的欄位,則必須給 int 欄位指定名字
面向對象編程-多重繼承
多重繼承說明
如 一個 struct 嵌套了多個匿名結構體,那麼該結構體可以直接訪問嵌套的匿名結構體的欄位和方
法, 從而實現了多重繼承。
多重繼承細節說明
1) 如嵌入的匿名結構體有相同的欄位名或者方法名,則在訪問時,需要通過匿名結構體類型名來
區分。【案例演示】
2) 為了保證代碼的簡潔性,建議大家儘量不使用多重繼承
package main
import (
"fmt"
)
type A struct {
Name string
age int
}
type B struct {
Name string
Score float64
}
type C struct {
A
B
//Name string
}
type D struct {
a A //有名結構體
}
type Goods struct {
Name string
Price float64
}
type Brand struct {
Name string
Address string
}
type TV struct {
Goods
Brand
}
type TV2 struct {
*Goods
*Brand
}
type Monster struct {
Name string
Age int
}
type E struct {
Monster
int
n int
}
func main() {
var c C
//如果c 沒有Name欄位,而A 和 B有Name, 這時就必須通過指定匿名結構體名字來區分
//所以 c.Name 就會包編譯錯誤, 這個規則對方法也是一樣的!
c.A.Name = "tom" // error
fmt.Println("c")
//如果D 中是一個有名結構體,則訪問有名結構體的欄位時,就必須帶上有名結構體的名字
//比如 d.a.Name
var d D
d.a.Name = "jack"
//嵌套匿名結構體後,也可以在創建結構體變數(實例)時,直接指定各個匿名結構體欄位的值
tv := TV{ Goods{"電視機001", 5000.99}, Brand{"海爾", "山東"}, }
//演示訪問Goods的Name
fmt.Println(tv.Goods.Name)
fmt.Println(tv.Price)
tv2 := TV{
Goods{
Price : 5000.99,
Name : "電視機002",
},
Brand{
Name : "夏普",
Address :"北京",
},
}
fmt.Println("tv", tv)
fmt.Println("tv2", tv2)
tv3 := TV2{ &Goods{"電視機003", 7000.99}, &Brand{"創維", "河南"}, }
tv4 := TV2{
&Goods{
Name : "電視機004",
Price : 9000.99,
},
&Brand{
Name : "長虹",
Address : "四川",
},
}
fmt.Println("tv3", *tv3.Goods, *tv3.Brand)
fmt.Println("tv4", *tv4.Goods, *tv4.Brand)
//演示一下匿名欄位時基本數據類型的使用
var e E
e.Name = "狐狸精"
e.Age = 300
e.int = 20
e.n = 40
fmt.Println("e=", e)
}
介面(interface)
基本介紹
按順序,我們應該學習多態,但是在學習多態前,我們需要講解介面(interface),因為在 Golang 中 多態
特性主要是通過介面來體現的。
介面快速入門
這樣的設計需求在 Golang 編程中也是會大量存在的,我曾經說過,一個程式就是一個世界,在現實世
界存在的情況,在程式中也會出現。 我們用程式來模擬一下前面的應用場景。
代碼實現
package main
import (
"fmt"
)
//聲明/定義一個介面
type Usb interface {
//聲明瞭兩個沒有實現的方法
Start()
Stop()
}
//聲明/定義一個介面
type Usb2 interface {
//聲明瞭兩個沒有實現的方法
Start()
Stop()
Test()
}
type Phone struct {
}
//讓Phone 實現 Usb介面的方法
func (p Phone) Start() {
fmt.Println("手機開始工作。。。")
}
func (p Phone) Stop() {
fmt.Println("手機停止工作。。。")
}
type Camera struct {
}
//讓Camera 實現 Usb介面的方法
func (c Camera) Start() {
fmt.Println("相機開始工作~~~。。。")
}
func (c Camera) Stop() {
fmt.Println("相機停止工作。。。")
}
//電腦
type Computer struct {
}
//編寫一個方法Working 方法,接收一個Usb介面類型變數
//只要是實現了 Usb介面 (所謂實現Usb介面,就是指實現了 Usb介面聲明所有方法)
func (c Computer) Working(usb Usb) {
//通過usb介面變數來調用Start和Stop方法
usb.Start()
usb.Stop()
}
func main() {
//測試
//先創建結構體變數
computer := Computer{}
phone := Phone{}
camera := Camera{}
//關鍵點
computer.Working(phone)
computer.Working(camera) //
}
介面概念的再說明
interface 類型可以定義一組方法,但是這些不需要實現。並且 interface 不能包含任何變數。到某個
自定義類型(比如結構體 Phone)要使用的時候,在根據具體情況把這些方法寫出來(實現)。
說明
1) 介面里的 所有方法都沒有方法體,即介面的方法都是沒有實現的方法。介面體現了程式設計的
多態和 高內聚低偶合的思想。
2) Golang 中的介面, 不需要 顯式的實現。只要一個變數,含有介面類型中的所有方法,那麼這個
變數就實現這個介面。因此,Golang 中 沒有 implement 這樣的關鍵字
註意事項和細節
1) 介面本身 不能創建實例,但是 可以指向一個實現了該介面的自定義類型的變數(實例)
2) 介面中所有的方法都沒有方法體,即都是沒有實現的方法。
3) 在 Golang 中,一個自定義類型需要將某個介面的所有方法都實現,我們說這個自定義類型實現了該介面。
4) 一個自定義類型只有實現了某個介面,才能將該自定義類型的實例(變數)賦給介面類型
5) 只要是自定義數據類型,就可以實現介面,不僅僅是結構體類型。
6) 一個自定義類型可以實現多個介面
7) Golang 介面中不能有任何變數
8) 一個介面(比如 A 介面)可以繼承多個別的介面(比如 B,C 介面),這時如果要實現 A 介面,也必須將 B,C 介面的方法也全部實現。
9) interface 類型預設是一個指針(引用類型),如果沒有對 interface 初始化就使用,那麼會輸出 nil
10) 空介面 interface{} 沒有任何方法, 所以所有類型都實現了空接 口, 即我們可以 把任何一個變數
賦給空介面。
package main
import (
"fmt"
)
type Stu struct {
Name string
}
func (stu Stu) Say() {
fmt.Println("Stu Say()")
}
type integer int
func (i integer) Say() {
fmt.Println("integer Say i =" ,i )
}
type AInterface interface {
Say()
}
type BInterface interface {
Hello()
}
type Monster struct {
}
func (m Monster) Hello() {
fmt.Println("Monster Hello()~~")
}
func (m Monster) Say() {
fmt.Println("Monster Say()~~")
}
func main() {
var stu Stu //結構體變數,實現了 Say() 實現了 AInterface
var a AInterface = stu
a.Say()
var i integer = 10
var b AInterface = i
b.Say() // integer Say i = 10
//Monster實現了AInterface 和 BInterface
var monster Monster
var a2 AInterface = monster
var b2 BInterface = monster
a2.Say()
b2.Hello()
}
package main
import (
"fmt"
)
type BInterface interface {
test01()
}
type CInterface interface {
test02()
}
type AInterface interface {
BInterface
CInterface
test03()
}
//如果需要實現AInterface,就需要將BInterface CInterface的方法都實現
type Stu struct {
}
func (stu Stu) test01() {
}
func (stu Stu) test02() {
}
func (stu Stu) test03() {
}
type T interface{
}
func main() {
var stu Stu
var a AInterface = stu
a.test01()
var t T = stu //ok
fmt.Println(t)
var t2 interface{} = stu
var num1 float64 = 8.8
t2 = num1
t = num1
fmt.Println(t2, t)
}
package main
import "fmt"
type Usb interface {
Say()
}
type Stu struct {
}
func (this *Stu) Say() {
fmt.Println("Say()")
}
func main() {
var stu Stu = Stu{}
// 錯誤! 會報 Stu類型沒有實現Usb介面 ,
// 如果希望通過編譯, var u Usb = &stu
var u Usb = stu
u.Say()
fmt.Println("here", u)
}
介面編程的最佳實踐
實現對 Hero 結構體切片的排序: sort.Sort(data Interface)
package main
import (
"fmt"
"sort"
"math/rand"
)
//1.聲明Hero結構體
type Hero struct{
Name string
Age int
}
//2.聲明一個Hero結構體切片類型
type HeroSlice []Hero
//3.實現Interface 介面
func (hs HeroSlice) Len() int {
return len(hs)
}
//Less方法就是決定你使用什麼標準進行排序
//1. 按Hero的年齡從小到大排序!!
func (hs HeroSlice) Less(i, j int) bool {
return hs[i].Age < hs[j].Age
//修改成對Name排序
//return hs[i].Name < hs[j].Name
}
func (hs HeroSlice) Swap(i, j int) {
//交換
// temp := hs[i]
// hs[i] = hs[j]
// hs[j] = temp
//下麵的一句話等價於三句話
hs[i], hs[j] = hs[j], hs[i]
}
//1.聲明Student結構體
type Student struct{
Name string
Age int
Score float64
}
//將Student的切片,安Score從大到小排序!!
func main() {
//先定義一個數組/切片
var intSlice = []int{0, -1, 10, 7, 90}
//要求對 intSlice切片進行排序
//1. 冒泡排序...
//2. 也可以使用系統提供的方法
sort.Ints(intSlice)
fmt.Println(intSlice)
//請大家對結構體切片進行排序
//1. 冒泡排序...
//2. 也可以使用系統提供的方法
//測試看看我們是否可以對結構體切片進行排序
var heroes HeroSlice
for i := 0; i < 10 ; i++ {
hero := Hero{
Name : fmt.Sprintf("英雄|%d", rand.Intn(100)),
Age : rand.Intn(100),
}
//將 hero append到 heroes切片
heroes = append(heroes, hero)
}
//看看排序前的順序
for _ , v := range heroes {
fmt.Println(v)
}
//調用sort.Sort
sort.Sort(heroes)
fmt.Println("-----------排序後------------")
//看看排序後的順序
for _ , v := range heroes {
fmt.Println(v)
}
i := 10
j := 20
i, j = j, i
fmt.Println("i=", i, "j=", j) // i=20 j = 10
}
實現介面 vs 繼承
大家可能會對實現介面和繼承比較迷茫了, 那麼他們究竟有什麼區別呢
package main
import (
"fmt"
)
//Monkey結構體
type Monkey struct {
Name string
}
//聲明介面
type BirdAble interface {
Flying()
}
type FishAble interface {
Swimming()
}
func (this *Monkey) climbing() {
fmt.Println(this.Name, " 生來會爬樹..")
}
//LittleMonkey結構體
type LittleMonkey struct {
Monkey //繼承
}
//讓LittleMonkey實現BirdAble
func (this *LittleMonkey) Flying() {
fmt.Println(this.Name, " 通過學習,會飛翔...")
}
//讓LittleMonkey實現FishAble
func (this *LittleMonkey) Swimming() {
fmt.Println(this.Name, " 通過學習,會游泳..")
}
func main() {
//創建一個LittleMonkey 實例
monkey := LittleMonkey{
Monkey {
Name : "悟空",
},
}
monkey.climbing()
monkey.Flying()
monkey.Swimming()
}
1) 當 A 結構體繼承了 B 結構體,那麼 A 結構就自動的繼承了 B 結構體的欄位和方法,並且可以直
接使用
2) 當 A 結構體需要擴展功能,同時不希望去破壞繼承關係,則可以去實現某個介面即可,因此我們可以認為:實現介面是對繼承機制的補充.實現介面可以看作是對 繼承的一種補充
介面和繼承解決的解決的問題不同
繼承的價值主要在於:解決代碼的 復用性和 可維護性。
介面的價值主要在於: 設計,設計好各種規範(方法),讓其它自定義類型去實現這些方法。
介面比繼承更加靈活 Person Student BirdAble LittleMonkey
介面比繼承更加靈活,繼承是滿足 is - a 的關係,而介面只需滿足 like - a 的關係。
介面在一定程度上實現 代碼解耦
面向對象編程-多態
基本介紹
變數(實例)具有多種形態。面向對象的第三大特征,在 Go 語言,多態特征是通過介面實現的。可以按照統一的介面來調用不同的實現。這時介面變數就呈現不同的形態。
快速入門
在前面的 Usb 介面案例,Usb usb ,既可以接收手機變數,又可以接收相機變數,就體現了 Usb 介面 多態特性。
介面體現多態的兩種形式
多態參數
在前面的 Usb 介面案例,Usb usb ,即可以接收手機變數,又可以接收相機變數,就體現了 Usb 接多態。
多態數組
演示一個案例:給 Usb 數組中,存放 Phone 結構體 和 Camera 結構體變數
案例說明:
package main
import (
"fmt"
)
//聲明/定義一個介面
type Usb interface {
//聲明瞭兩個沒有實現的方法
Start()
Stop()
}
type Phone struct {
name string
}
//讓Phone 實現 Usb介面的方法
func (p Phone) Start() {
fmt.Println("手機開始工作。。。")
}
func (p Phone) Stop() {
fmt.Println("手機停止工作。。。")
}
type Camera struct {
name string
}
//讓Camera 實現 Usb介面的方法
func (c Camera) Start() {
fmt.Println("相機開始工作。。。")
}
func (c Camera) Stop() {
fmt.Println("相機停止工作。。。")
}
func main() {
//定義一個Usb介面數組,可以存放Phone和Camera的結構體變數
//這裡就體現出多態數組
var usbArr [3]Usb
usbArr[0] = Phone{"vivo"}
usbArr[1] = Phone{"小米"}
usbArr[2] = Camera{"尼康"}
fmt.Println(usbArr)
}
類型斷言
由一個具體的需要,引出了類型斷言
基本介紹
類型斷言,由於介面是一般類型,不知道具體類型,如果要轉成具體類型,就需要使用類型斷言,
具體的如下:
package main
import (
"fmt"
)
type Point struct {
x int
y int
}
func main() {
var a interface{}
var point Point = Point{1, 2}
a = point //oK
// 如何將 a 賦給一個Point變數?
var b Point
// b = a 不可以
// b = a.(Point) // 可以
b = a.(Point)
fmt.Println(b) //
//類型斷言的其它案例
// var x interface{}
// var b2 float32 = 1.1
// x = b2 //空介面,可以接收任意類型
// // x=>float32 [使用類型斷言]
// y := x.(float32)
// fmt.Printf("y 的類型是 %T 值是=%v", y, y)
//類型斷言(帶檢測的)
var x interface{}
var b2 float32 = 2.1
x = b2 //空介面,可以接收任意類型
// x=>float32 [使用類型斷言]
//類型斷言(帶檢測的)
if y, ok := x.(float32); ok {
fmt.Println("convert success")
fmt.Printf("y 的類型是 %T 值是=%v", y, y)
} else {
fmt.Println("convert fail")
}
fmt.Println("繼續執行...")
}
對上面代碼的說明:
在進行類型斷言時,如果類型不匹配,就會報 panic, 因此進行類型斷言時,要確保原來的空介面
指向的就是斷言的類型.
如何在進行斷言時,帶上檢測機制,如果成功就 ok,否則也不要報 panic
類型斷言的最佳實踐 1
在前面的 Usb 介面案例做改進:
給 Phone 結構體增加一個特有的方法 call(), 當 Usb 介面接收的是 Phone 變數時,還需要調用 call
方法, 走代碼:
package main
import (
"fmt"
)
//聲明/定義一個介面
type Usb interface {
//聲明瞭兩個沒有實現的方法
Start()
Stop()
}
type Phone struct {
name string
}
//讓Phone 實現 Usb介面的方法
func (p Phone) Start() {
fmt.Println("手機開始工作。。。")
}
func (p Phone) Stop() {
fmt.Println("手機停止工作。。。")
}
func (p Phone) Call() {
fmt.Println("手機 在打電話..")
}
type Camera struct {
name string
}
//讓Camera 實現 Usb介面的方法
func (c Camera) Start() {
fmt.Println("相機開始工作。。。")
}
func (c Camera) Stop() {
fmt.Println("相機停止工作。。。")
}
type Computer struct {
}
func (computer Computer) Working(usb Usb) {
usb.Start()
//如果usb是指向Phone結構體變數,則還需要調用Call方法
//類型斷言..[註意體會!!!]
if phone, ok := usb.(Phone); ok {
phone.Call()
}
usb.Stop()
}
func main() {
//定義一個Usb介面數組,可以存放Phone和Camera的結構體變數
//這裡就體現出多態數組
var usbArr [3]Usb
usbArr[0] = Phone{"vivo"}
usbArr[1] = Phone{"小米"}
usbArr[2] = Camera{"尼康"}
//遍歷usbArr
//Phone還有一個特有的方法call(),請遍歷Usb數組,如果是Phone變數,
//除了調用Usb 介面聲明的方法外,還需要調用Phone 特有方法 call. =》類型斷言
var computer Computer
for _, v := range usbArr{
computer.Working(v)
fmt.Println()
}
//fmt.Println(usbArr)
}
類型斷言的最佳實踐 2
寫一函數,迴圈判斷傳入參數的類型:
package main
import (
"fmt"
)
//定義Student類型
type Student struct {
}
//編寫一個函數,可以判斷輸入的參數是什麼類型
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個參數是 整數 類型,值是%v\n", index, x)
case string :
fmt.Printf("第%v個參數是 string 類型,值是%v\n", index, x)
case Student :
fmt.Printf("第%v個參數是 Student 類型,值是%v\n", index, x)
case *Student :
fmt.Printf("第%v個參數是 *Student 類型,值是%v\n", index, x)
default :
fmt.Printf("第%v個參數是 類型 不確定,值是%v\n", index, x)
}
}
}
func main() {
var n1 float32 = 1.1
var n2 float64 = 2.3
var n3 int32 = 30
var name string = "tom"
address := "北京"
n4 := 300
stu1 := Student{}
stu2 := &Student{}
TypeJudge(n1, n2, n3, name, address, n4, stu1, stu2)
}
go的面向對象終於完了(^▽^)!~~~~