go-面向對象編程(下)

来源:https://www.cnblogs.com/ygjzs/archive/2019/11/17/11874869.html
-Advertisement-
Play Games

面向對象編程思想 抽象 抽象的介紹 我們在前面去定義一個結構體時候,實際上就是把一類事物的共有的 屬性( 欄位)和 行為( 方法)提取 出來,形成一個 物理模型(結構體)。這種研究問題的方法稱為抽象 比如一個銀行賬戶: 面向對象編程三大特性 封裝 基本介紹 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的面向對象終於完了(^▽^)!~~~~


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Object-fit 我們有時候瀏覽一些網站的時候,偶爾會遇到這種情況: 明顯它喵的形變了,尤其是這種這麼業餘的失誤,還是出現在一個專門做圖片的網站上。 產生這種現象的原因是:圖片寫了固定的寬高,這個寬高形成了一個固定的比例,然而圖片本身不可能總是按照你規定的比例上傳上來,只要比例有一點不對,就會產 ...
  • 1.判斷undefined: var tmp = undefined; if (typeof(tmp) == "undefined"){ console.log("undefined"); } 說明:typeof 返回的是字元串,有六種可能:"number"、"string"、"boolean"、" ...
  • 預設安裝後是英文版 view-show console 安裝packagecontrol https://packagecontrol.io/installation ctrl+`打開控制台,輸入代碼後回車。 安裝後,工具->命令面板或快捷鍵按ctrl+shift+p,打開packagecontro ...
  • 作者:Yazeed Bzadough 譯者:小維FE 原文:freecodecamp 為了保證文章的可讀性,本文采用意譯而非直譯。 90%的規約,10%的庫。 Redux是迄今為止創建的最重要的JavaScript庫之一,靈感來源於以前的藝術比如 "Flux" 和 "Elm" ,Redux通過引入一 ...
  • 場景 Nginx入門簡介和反向代理、負載均衡、動靜分離理解 https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/102790862 Ubuntu Server 16.04 LTS上怎樣安裝下載安裝Nginx並啟動: https://bl ...
  • 一份擁有良好可讀性和拓展性的代碼是項目里的良藥,它不僅看著舒服,改起來也方便,甚至還能重用,各模塊邏輯分明。“見碼知功底”,而要達到高手那種簡潔有力的境界,需要進行大量的總結和練習,今天我們就來談談如何寫出優美的代碼。 命名 好的命名應該具有如下特征: 1,意思正確。這是最基本的要求,不要掛羊頭賣狗 ...
  • 例10 最大公約數 問題描述 有三個正整數a,b,c(0<a,b,c<10^6),其中c不等於b。若a和c的最大公約數為b,現已知a和b,求滿足條件的最小的c。 輸入數據 第一行輸入一個n,表示有n組測試數據,接下來的n行,每行輸入兩個正整數a,b。 輸出格式 輸出對應的c,每組測試數據占一行。 輸 ...
  • 例9 生理周期 問題描述 人生來就有三個生理周期,分別為體力、感情和智力周期,它們的周期長度為 23 天、28 天和33 天。每一個周期中有一天是高峰。在高峰這天,人會在相應的方面表現出色。例如,智力周期的高峰,人會思維敏捷,精力容易高度集中。因為三個周期的周長不同,所以通常三個周期的高峰不會落在同 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...