1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Go 經(jīng)典入門系列 18:接口(一)

        共 8497字,需瀏覽 17分鐘

         ·

        2020-12-22 13:59

        點(diǎn)擊上方藍(lán)色“Go語言中文網(wǎng)”關(guān)注,每天一起學(xué) Go

        歡迎來到 Golang 系列教程[1]的第 18 個(gè)教程。接口共有兩個(gè)教程,這是我們接口的第一個(gè)教程。

        什么是接口?

        在面向?qū)ο蟮念I(lǐng)域里,接口一般這樣定義:接口定義一個(gè)對象的行為。接口只指定了對象應(yīng)該做什么,至于如何實(shí)現(xiàn)這個(gè)行為(即實(shí)現(xiàn)細(xì)節(jié)),則由對象本身去確定。

        在 Go 語言中,接口就是方法簽名(Method Signature)的集合。當(dāng)一個(gè)類型定義了接口中的所有方法,我們稱它實(shí)現(xiàn)了該接口。這與面向?qū)ο缶幊蹋∣OP)的說法很類似。接口指定了一個(gè)類型應(yīng)該具有的方法,并由該類型決定如何實(shí)現(xiàn)這些方法。

        例如,WashingMachine 是一個(gè)含有 Cleaning()Drying() 兩個(gè)方法的接口。任何定義了 Cleaning()Drying() 的類型,都稱它實(shí)現(xiàn)了 WashingMachine 接口。

        接口的聲明與實(shí)現(xiàn)

        讓我們編寫代碼,創(chuàng)建一個(gè)接口并且實(shí)現(xiàn)它。

        package?main

        import?(
        ????"fmt"
        )

        //interface?definition
        type?VowelsFinder?interface?{
        ????FindVowels()?[]rune
        }

        type?MyString?string

        //MyString?implements?VowelsFinder
        func?(ms?MyString)?FindVowels()?[]rune?{
        ????var?vowels?[]rune
        ????for?_,?rune?:=?range?ms?{
        ????????if?rune?==?'a'?||?rune?==?'e'?||?rune?==?'i'?||?rune?==?'o'?||?rune?==?'u'?{
        ????????????vowels?=?append(vowels,?rune)
        ????????}
        ????}
        ????return?vowels
        }

        func?main()?{
        ????name?:=?MyString("Sam?Anderson")
        ????var?v?VowelsFinder
        ????v?=?name?//?possible?since?MyString?implements?VowelsFinder
        ????fmt.Printf("Vowels?are?%c",?v.FindVowels())

        }

        在線運(yùn)行程序[2]

        在上面程序的第 8 行,創(chuàng)建了一個(gè)名為 VowelsFinder 的接口,該接口有一個(gè) FindVowels() []rune 的方法。

        在接下來的一行,我們創(chuàng)建了一個(gè) MyString 類型。

        在第 15 行,我們給接受者類型(Receiver Type) MyString 添加了方法 FindVowels() []rune。現(xiàn)在,我們稱 MyString 實(shí)現(xiàn)了 VowelsFinder 接口。這就和其他語言(如 Java)很不同,其他一些語言要求一個(gè)類使用 implement 關(guān)鍵字,來顯式地聲明該類實(shí)現(xiàn)了接口。而在 Go 中,并不需要這樣。如果一個(gè)類型包含了接口中聲明的所有方法,那么它就隱式地實(shí)現(xiàn)了 Go 接口。

        在第 28 行,v 的類型為 VowelsFinder,name 的類型為 MyString,我們把 name 賦值給了 v。由于 MyString 實(shí)現(xiàn)了 VowelFinder,因此這是合法的。在下一行,v.FindVowels() 調(diào)用了 MyString 類型的 FindVowels 方法,打印字符串 Sam Anderson 里所有的元音。該程序輸出 Vowels are [a e o]

        祝賀!你已經(jīng)創(chuàng)建并實(shí)現(xiàn)了你的第一個(gè)接口。

        接口的實(shí)際用途

        前面的例子教我們創(chuàng)建并實(shí)現(xiàn)了接口,但還沒有告訴我們接口的實(shí)際用途。在上面的程序里,如果我們使用 name.FindVowels(),而不是 v.FindVowels(),程序依然能夠照常運(yùn)行,但接口并沒有體現(xiàn)出實(shí)際價(jià)值。

        因此,我們現(xiàn)在討論一下接口的實(shí)際應(yīng)用場景。

        我們編寫一個(gè)簡單程序,根據(jù)公司員工的個(gè)人薪資,計(jì)算公司的總支出。為了簡單起見,我們假定支出的單位都是美元。

        package?main

        import?(
        ????"fmt"
        )

        type?SalaryCalculator?interface?{
        ????CalculateSalary()?int
        }

        type?Permanent?struct?{
        ????empId????int
        ????basicpay?int
        ????pf???????int
        }

        type?Contract?struct?{
        ????empId??int
        ????basicpay?int
        }

        //salary?of?permanent?employee?is?sum?of?basic?pay?and?pf
        func?(p?Permanent)?CalculateSalary()?int?{
        ????return?p.basicpay?+?p.pf
        }

        //salary?of?contract?employee?is?the?basic?pay?alone
        func?(c?Contract)?CalculateSalary()?int?{
        ????return?c.basicpay
        }

        /*
        total?expense?is?calculated?by?iterating?though?the?SalaryCalculator?slice?and?summing
        the?salaries?of?the?individual?employees
        */

        func?totalExpense(s?[]SalaryCalculator)?{
        ????expense?:=?0
        ????for?_,?v?:=?range?s?{
        ????????expense?=?expense?+?v.CalculateSalary()
        ????}
        ????fmt.Printf("Total?Expense?Per?Month?$%d",?expense)
        }

        func?main()?{
        ????pemp1?:=?Permanent{1,?5000,?20}
        ????pemp2?:=?Permanent{2,?6000,?30}
        ????cemp1?:=?Contract{3,?3000}
        ????employees?:=?[]SalaryCalculator{pemp1,?pemp2,?cemp1}
        ????totalExpense(employees)

        }

        在線運(yùn)行程序[3]

        上面程序的第 7 行聲明了一個(gè) SalaryCalculator 接口類型,它只有一個(gè)方法 CalculateSalary() int。

        在公司里,我們有兩類員工,即第 11 行和第 17 行定義的結(jié)構(gòu)體:PermanentContract。長期員工(Permanent)的薪資是 basicpaypf 相加之和,而合同員工(Contract)只有基本工資 basicpay。在第 23 行和第 28 行中,方法 CalculateSalary 分別實(shí)現(xiàn)了以上關(guān)系。由于 PermanentContract 都聲明了該方法,因此它們都實(shí)現(xiàn)了 SalaryCalculator 接口。

        第 36 行聲明的 totalExpense 方法體現(xiàn)出了接口的妙用。該方法接收一個(gè) SalaryCalculator 接口的切片([]SalaryCalculator)作為參數(shù)。在第 49 行,我們向 totalExpense 方法傳遞了一個(gè)包含 PermanentContact 類型的切片。在第 39 行中,通過調(diào)用不同類型對應(yīng)的 CalculateSalary 方法,totalExpense 可以計(jì)算得到支出。

        這樣做最大的優(yōu)點(diǎn)是:totalExpense 可以擴(kuò)展新的員工類型,而不需要修改任何代碼。假如公司增加了一種新的員工類型 Freelancer,它有著不同的薪資結(jié)構(gòu)。Freelancer只需傳遞到 totalExpense 的切片參數(shù)中,無需 totalExpense 方法本身進(jìn)行修改。只要 Freelancer 也實(shí)現(xiàn)了 SalaryCalculator 接口,totalExpense 就能夠?qū)崿F(xiàn)其功能。

        該程序輸出 Total Expense Per Month $14050。

        接口的內(nèi)部表示

        我們可以把接口看作內(nèi)部的一個(gè)元組 (type, value)type 是接口底層的具體類型(Concrete Type),而 value 是具體類型的值。

        我們編寫一個(gè)程序來更好地理解它。

        package?main

        import?(
        ????"fmt"
        )

        type?Test?interface?{
        ????Tester()
        }

        type?MyFloat?float64

        func?(m?MyFloat)?Tester()?{
        ????fmt.Println(m)
        }

        func?describe(t?Test)?{
        ????fmt.Printf("Interface?type?%T?value?%v\n",?t,?t)
        }

        func?main()?{
        ????var?t?Test
        ????f?:=?MyFloat(89.7)
        ????t?=?f
        ????describe(t)
        ????t.Tester()
        }

        在線運(yùn)行程序[4]

        Test 接口只有一個(gè)方法 Tester(),而 MyFloat 類型實(shí)現(xiàn)了該接口。在第 24 行,我們把變量 fMyFloat 類型)賦值給了 tTest 類型)?,F(xiàn)在 t 的具體類型為 MyFloat,而 t 的值為 89.7。第 17 行的 describe 函數(shù)打印出了接口的具體類型和值。該程序輸出:

        Interface?type?main.MyFloat?value?89.7
        89.7

        空接口

        沒有包含方法的接口稱為空接口。空接口表示為 interface{}。由于空接口沒有方法,因此所有類型都實(shí)現(xiàn)了空接口。

        package?main

        import?(
        ????"fmt"
        )

        func?describe(i?interface{})?{
        ????fmt.Printf("Type?=?%T,?value?=?%v\n",?i,?i)
        }

        func?main()?{
        ????s?:=?"Hello?World"
        ????describe(s)
        ????i?:=?55
        ????describe(i)
        ????strt?:=?struct?{
        ????????name?string
        ????}{
        ????????name:?"Naveen?R",
        ????}
        ????describe(strt)
        }

        在線運(yùn)行程序[5]

        在上面的程序的第 7 行,describe(i interface{}) 函數(shù)接收空接口作為參數(shù),因此,可以給這個(gè)函數(shù)傳遞任何類型。

        在第 13 行、第 15 行和第 21 行,我們分別給 describe 函數(shù)傳遞了 string、intstruct。該程序打?。?/p>

        Type?=?string,?value?=?Hello?World
        Type?=?int,?value?=?55
        Type?=?struct?{?name?string?},?value?=?{Naveen?R}

        類型斷言

        類型斷言用于提取接口的底層值(Underlying Value)。

        在語法 i.(T) 中,接口 i 的具體類型是 T,該語法用于獲得接口的底層值。

        一段代碼勝過千言。下面編寫個(gè)關(guān)于類型斷言的程序。

        package?main

        import?(
        ????"fmt"
        )

        func?assert(i?interface{})?{
        ????s?:=?i.(int)?//get?the?underlying?int?value?from?i
        ????fmt.Println(s)
        }
        func?main()?{
        ????var?s?interface{}?=?56
        ????assert(s)
        }

        在線運(yùn)行程序[6]

        在第 12 行,s 的具體類型是 int。在第 8 行,我們使用了語法 i.(int) 來提取 i 的底層 int 值。該程序會打印 56。

        在上面程序中,如果具體類型不是 int,會發(fā)生什么呢?接下來看看。

        package?main

        import?(
        ????"fmt"
        )

        func?assert(i?interface{})?{
        ????s?:=?i.(int)
        ????fmt.Println(s)
        }
        func?main()?{
        ????var?s?interface{}?=?"Steven?Paul"
        ????assert(s)
        }

        在線運(yùn)行程序[7]

        在上面程序中,我們把具體類型為 strings 傳遞給了 assert 函數(shù),試圖從它提取出 int 值。該程序會報(bào)錯(cuò):panic: interface conversion: interface {} is string, not int.。

        要解決該問題,我們可以使用以下語法:

        v,?ok?:=?i.(T)

        如果 i 的具體類型是 T,那么 v 賦值為 i 的底層值,而 ok 賦值為 true。

        如果 i 的具體類型不是 T,那么 ok 賦值為 falsev 賦值為 T 類型的零值,此時(shí)程序不會報(bào)錯(cuò)

        package?main

        import?(
        ????"fmt"
        )

        func?assert(i?interface{})?{
        ????v,?ok?:=?i.(int)
        ????fmt.Println(v,?ok)
        }
        func?main()?{
        ????var?s?interface{}?=?56
        ????assert(s)
        ????var?i?interface{}?=?"Steven?Paul"
        ????assert(i)
        }

        在線運(yùn)行程序[8]

        當(dāng)給 assert 函數(shù)傳遞 Steven Paul 時(shí),由于 i 的具體類型不是 int,ok 賦值為 false,而 v 賦值為 0(int 的零值)。該程序打印:

        56?true
        0?false

        類型選擇(Type Switch)

        類型選擇用于將接口的具體類型與很多 case 語句所指定的類型進(jìn)行比較。它與一般的 switch 語句類似。唯一的區(qū)別在于類型選擇指定的是類型,而一般的 switch 指定的是值。

        類型選擇的語法類似于類型斷言。類型斷言的語法是 i.(T),而對于類型選擇,類型 T 由關(guān)鍵字 type 代替。下面看看程序是如何工作的。

        package?main

        import?(
        ????"fmt"
        )

        func?findType(i?interface{})?{
        ????switch?i.(type)?{
        ????case?string:
        ????????fmt.Printf("I?am?a?string?and?my?value?is?%s\n",?i.(string))
        ????case?int:
        ????????fmt.Printf("I?am?an?int?and?my?value?is?%d\n",?i.(int))
        ????default:
        ????????fmt.Printf("Unknown?type\n")
        ????}
        }
        func?main()?{
        ????findType("Naveen")
        ????findType(77)
        ????findType(89.98)
        }

        在線運(yùn)行程序[9]

        在上述程序的第 8 行,switch i.(type) 表示一個(gè)類型選擇。每個(gè) case 語句都把 i 的具體類型和一個(gè)指定類型進(jìn)行了比較。如果 case 匹配成功,會打印出相應(yīng)的語句。該程序輸出:

        I?am?a?string?and?my?value?is?Naveen
        I?am?an?int?and?my?value?is?77
        Unknown?type

        第 20 行中的 89.98 的類型是 float64,沒有在 case 上匹配成功,因此最后一行打印了 Unknown type

        還可以將一個(gè)類型和接口相比較。如果一個(gè)類型實(shí)現(xiàn)了接口,那么該類型與其實(shí)現(xiàn)的接口就可以互相比較。

        為了闡明這一點(diǎn),下面寫一個(gè)程序。

        package?main

        import?"fmt"

        type?Describer?interface?{
        ????Describe()
        }
        type?Person?struct?{
        ????name?string
        ????age??int
        }

        func?(p?Person)?Describe()?{
        ????fmt.Printf("%s?is?%d?years?old",?p.name,?p.age)
        }

        func?findType(i?interface{})?{
        ????switch?v?:=?i.(type)?{
        ????case?Describer:
        ????????v.Describe()
        ????default:
        ????????fmt.Printf("unknown?type\n")
        ????}
        }

        func?main()?{
        ????findType("Naveen")
        ????p?:=?Person{
        ????????name:?"Naveen?R",
        ????????age:??25,
        ????}
        ????findType(p)
        }

        在線運(yùn)行程序[10]

        在上面程序中,結(jié)構(gòu)體 Person 實(shí)現(xiàn)了 Describer 接口。在第 19 行的 case 語句中,v 與接口類型 Describer 進(jìn)行了比較。p 實(shí)現(xiàn)了 Describer,因此滿足了該 case 語句,于是當(dāng)程序運(yùn)行到第 32 行的 findType(p) 時(shí),程序調(diào)用了 Describe() 方法。

        該程序輸出:

        unknown?type
        Naveen?R?is?25?years?old

        接口(一)的內(nèi)容到此結(jié)束。在接口(二)中我們還會繼續(xù)討論接口。祝您愉快!

        上一教程 - 方法

        下一教程 - 接口 - II[11]


        via: https://golangbot.com/interfaces-part-1/

        作者:Nick Coghlan[12]譯者:Noluye[13]校對:polaris1119[14]

        本文由 GCTT[15] 原創(chuàng)編譯,Go 中文網(wǎng)[16] 榮譽(yù)推出

        參考資料

        [1]

        Golang 系列教程: https://studygolang.com/subject/2

        [2]

        在線運(yùn)行程序: https://play.golang.org/p/F-T3S_wNNB

        [3]

        在線運(yùn)行程序: https://play.golang.org/p/5t6GgQ2TSU

        [4]

        在線運(yùn)行程序: https://play.golang.org/p/Q40Omtewlh

        [5]

        在線運(yùn)行程序: https://play.golang.org/p/Fm5KescoJb

        [6]

        在線運(yùn)行程序: https://play.golang.org/p/YstKXEeSBL

        [7]

        在線運(yùn)行程序: https://play.golang.org/p/88KflSceHK

        [8]

        在線運(yùn)行程序: https://play.golang.org/p/0sB-KlVw8A

        [9]

        在線運(yùn)行程序: https://play.golang.org/p/XYPDwOvoCh

        [10]

        在線運(yùn)行程序: https://play.golang.org/p/o6aHzIz4wC

        [11]

        接口 - II: https://studygolang.com/articles/12325

        [12]

        Nick Coghlan: https://golangbot.com/about/

        [13]

        Noluye: https://github.com/Noluye

        [14]

        polaris1119: https://github.com/polaris1119

        [15]

        GCTT: https://github.com/studygolang/GCTT

        [16]

        Go 中文網(wǎng): https://studygolang.com/



        推薦閱讀


        福利

        我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號 「polarisxu」,回復(fù) ebook 獲??;還可以回復(fù)「進(jìn)群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。

        瀏覽 52
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            最新中文av | 国产精品免费久久久久软件 | 精品人伦一区二区三区牛牛视频 | 丁香五香天堂网 | 视频黄页在线观看 | 中文字幕四区 | 性爱无码在线观看 | 91风间由美一区二区三区四区 | 欧洲亚洲在线 | 在线观看中文字幕一区 |