面向物件編程思想-抽象
抽象的介紹
我們在前面去定義一個結構體時候,實際上就是把一類事物的共有的 屬性( 欄位)和 行為( 方法)提取
出來,形成一個 物理模型(結構體),這種研究問題的方法稱為抽象
比如一個銀行賬戶:
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)就是把抽象出的欄位和對欄位的操作封裝在一起,資料被保護在內部,程式的其
它包只有通過被授權的操作(方法),才能對欄位進行操作
封裝的理解和好處
- 隱藏實作細節
- 提高對 資料進行驗證,保證安全合理(Age)
如何體現封裝
- 對結構體中的屬性進行封裝
- 通過 方法,包 包 實作封裝
封裝的實作步驟
- 將結構體、欄位(屬性)的首字母小寫(不能匯出了,其它包不能使用,類似 private)
- 給結構體所在包提供一個工廠模式的函式,首字母大寫,類似一個建構式
- 提供一個首字母大寫的 Set 方法(類似其它語言的 public),用于對屬性判斷并賦值
func (var 結構體型別名) SetXxx(引數串列) (回傳值串列) {
//加入資料驗證的業務邏輯
var.欄位 = 引數
} - 提供一個首字母大寫的 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,提出代碼復用的問題
- Pupil 和 Graduate 兩個結構體的欄位和方法幾乎,但是我們卻寫了相同的代碼, 代碼復用性不
強 - 出現代碼冗余,而且代碼 不利于維護,同時 也不利于功能的擴展,
- 解決方法-通過 繼承方式來解決
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
}
繼承給編程帶來的便利
- 代碼的復用性提高了
- 代碼的擴展性和維護性提高了
繼承的深入討論
- 結構體可以 使用嵌套匿名結構體所有的欄位和方法,即:首字母大寫或者小寫的欄位、方法,
都可以使用,【舉例說明】 - 匿名結構體欄位訪問可以簡化
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) 嵌套匿名結構體后,也可以在創建結構體變數(實體)時,直接指定各個 匿名結構體欄位的值
說明
- 如果一個結構體有 int 型別的匿名欄位,就不能第二個,
- 如果需要有多個 int 的欄位,則必須給 int 欄位指定名字
面向物件編程-多重繼承
多重繼承說明
如 一個 struct 嵌套了多個匿名結構體,那么該結構體可以直接訪問嵌套的匿名結構體的欄位和方
法, 從而實作了多重繼承,
多重繼承細節說明
- 如嵌入的匿名結構體有相同的欄位名或者方法名,則在訪問時,需要通過匿名結構體型別名來
區分,【案例演示】 - 為了保證代碼的簡潔性,建議大家盡量不使用多重繼承
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)要使用的時候,在根據具體情況把這些方法寫出來(實作),
說明
- 介面里的 所有方法都沒有方法體,即介面的方法都是沒有實作的方法,介面體現了程式設計的
多型和 高內聚低偶合的思想, - Golang 中的介面, 不需要 顯式的實作,只要一個變數,含有介面型別中的所有方法,那么這個
變數就實作這個介面,因此,Golang 中 沒有 implement 這樣的關鍵字
注意事項和細節
- 介面本身 不能創建實體,但是 可以指向一個實作了該介面的自定義型別的變數(實體)
- 介面中所有的方法都沒有方法體,即都是沒有實作的方法,
- 在 Golang 中,一個自定義型別需要將某個介面的所有方法都實作,我們說這個自定義型別實作了該介面,
- 一個自定義型別只有實作了某個介面,才能將該自定義型別的實體(變數)賦給介面型別
- 只要是自定義資料型別,就可以實作介面,不僅僅是結構體型別,
- 一個自定義型別可以實作多個介面
- Golang 介面中不能有任何變數
- 一個介面(比如 A 介面)可以繼承多個別的介面(比如 B,C 介面),這時如果要實作 A 介面,也必須將 B,C 介面的方法也全部實作,
- interface 型別默認是一個指標(參考型別),如果沒有對 interface 初始化就使用,那么會輸出 nil
- 空介面 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()
}
- 當 A 結構體繼承了 B 結構體,那么 A 結構就自動的繼承了 B 結構體的欄位和方法,并且可以直
接使用 - 當 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的面向物件終于完了(▽)!~~~~
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/63360.html
標籤:Go
下一篇:go-家庭收支記賬軟體例子
