go語言的接口
簡介
- 接口是雙方規定的一種合作協議,接口實現者不需要關心接口會被怎樣使用,調用者不需要關心接口的實現細節。接口是一種類型。也是一種抽象結構。不會暴露所含數據的格式、類型及結構。比如只要一台洗衣機有洗衣服的功能和甩干的功能,我們就稱為洗衣機,不關心屬性(數據),只關心行為(方法)。
- 接口和其他動態語言的鴨子模型有密切關系。比如說
python、javascript。任何類型,只要實現了該接口中的方法集,那么就屬於這個類型。 - 每個接口由數個方法組成。
接口的定義
格式:
type 接口類型名 interface {
方法名1( 參數列表1 ) 返回值列表1
方法名2( 參數列表2 ) 返回值列表2
…
}
- 接口類型名:使用
type將接口定義為自定義的類型名 - 方法名:當方法名首字母是大寫時,且這個接口類型名首字母也是大寫時,這個方法可以被接口所在的包之外的代碼訪問。
- 參數列表、返回值列表:參數列表和返回值列表的參數變量名可以被忽略
示例:
// 可以吃
type Eat interface {
eat()
}
看到這個接口時,只知道他有一個eat()方法。並不知道誰實現了這些接口,也不知道是怎么實現的這些這個方法的。
實現接口的條件
- 接口的方法與實現接口的類型方法格式一致
- 接口的方法與實現接口的類型方法數目保持一致。即接口中的所有方法均被實現。
package main
import (
"fmt"
)
// 定義一個鴨子類型接口
type Duck interface {
// 鴨子叫
GaGa()
// 鴨子走
YouYong()
}
// 定義一個小雞類型的結構體
type Chicken struct {
}
// 為小雞結構體實現GaGa
func (c Chicken) GaGa() {
fmt.Println("我是小雞,但我也會嘎嘎叫")
}
// 為小雞結構體實現YouYong
func (c Chicken) YouYong() {
fmt.Println("我是小雞,但我也會游泳")
}
func main() {
// 實例化一個小雞
c := new(Chicken)
// 初始化一個小雞小鴨合體類型
var duckChicken Duck = c
// 小雞小鴨開始嘎嘎
duckChicken.GaGa()
// 小雞小鴨開始游泳
duckChicken.YouYong()
}
可以看出來,duckChicken可以直接調用GaGa和YouYong。他並不知道這兩個方法內部怎么實現的。
值類型接收者和指針型接收者實現接口
package main
import (
"fmt"
)
// 定義一個汪汪叫的接口
type WangWager interface {
Wang()
}
// 定義一個狗結構體
type Dog struct {
name string
}
func (d Dog) Wang() {
fmt.Println("汪汪叫")
}
func main() {
d1 := Dog{"小狗1"}
d2 := &Dog{
"小狗2",
}
var w1 WangWager = d1
w1.Wang()
var w2 WangWager = d2
w2.Wang()
}
可以發現,使用值類型實現接口后,不管dog結構體實例化是指針型還是值類型。都可以賦值給該接口變量。因為go語言內部有對指針類型變量求值的語法糖。
但是指針型實現接口后,只能指針型變量賦值給接口變量。
類型與接口的關系
類型與接口有一對多和多對多的關系。
一(類型)對多(接口):
package main
import (
"fmt"
)
// 定義一個Writer接口
type Writer interface {
Writer(p []byte) (n int, err error)
}
// 定義一個Closer接口
type Closer interface {
Closer() error
}
// 定義一個socket結構體類型
type Socket struct {
}
// 為socket實現一個Writer()方法
func (s *Socket) Writer(p []byte) (n int, err error) {
fmt.Println("開始寫入")
return n, err
}
// 為socket實現一個Closer()方法
func (s *Socket) Closer() (err error) {
fmt.Println("開始關閉")
return err
}
// 定義一個函數,負責寫
func useWriter(w Writer) {
// 執行w接口的寫方法
_, _ := w.Writer(nil)
}
// 定義一個函數,負責關閉
func useCloser(c Closer) {
// 執行c接口的關閉方法
_ := c.Closer()
}
func main() {
fmt.Println("理解類型與接口的關系")
// 類型和接口之間有一對多和多對一的關系。
// 一個類型可以實現多個接口
// 實例化socker結構體
s := new(Socket)
useWriter(s)
useCloser(s)
}
可以看出兩個函數完全獨立,完全不知道對方的存在,也不知道使用自己的接口是socket使用的
多(類型)對一(接口)
接口的方法不一定要一個結構體類型完全實現,接口的方法可以通過結構體嵌入實現。
package main
import (
"fmt"
)
// 定義一個服務接口,實現了服務開啟和日志輸出的方法
type Service interface {
Start() // 啟動服務
Log(string) // 日志輸出
}
// 定義一個游戲服務結構體
type GameService struct {
Logger // 內嵌logger結構體
}
// 為游戲結構體實現游戲服務的啟動method
func (g *GameService) Start() {
fmt.Println("游戲服務啟動成功")
}
// 定義一個日志器結構體
type Logger struct {
}
// 為日志服務器結構體實現日志輸出的method
func (l *Logger) Log(s string) {
fmt.Println(s)
}
func main() {
// 多個類型可以實現相同的接口
var ser Service = new(GameService)
ser.Start()
ser.Log("日志輸出")
}
可以看出,服務接口下一個服務啟動功能和日志輸出功能,但是游戲服務類型並沒有實現日志輸出功能,而是間接通過內嵌日志類型來實現,日志類型實現了日志輸出功能,所有游戲服務類型實現的接口可以直接使用日志輸出功能。、
接口的嵌套組合
不僅類型與類型之間可以嵌套,接口與接口之間也可以嵌套。
package main
import (
"fmt"
)
// 定義一個Say接口
type Say interface {
Say(s string)
}
// 定義一個Run接口
type Run interface {
Run(n int)
}
// 定義一個Skill接口
type Skill interface {
// 嵌套了兩個接口
Say
Run
}
// 定義一個Person結構體
type Person struct {
}
// 為Person結構體實現Say方法
func (p *Person) Say(s string) {
fmt.Println("說話:", s)
}
// 為Person結構體實現Run方法
func (p *Person) Run(n int) {
fmt.Println("步數:", n)
}
func main() {
// 實現人的結構體的技能類型
var s Skill = new(Person)
s.Say("Life Is Short Let's Go")
s.Run(9999)
// 實現啞巴(人)結構體的技能類型,無法說話,只能跑步
var yaBa Run = new(Person)
yaBa.Run(100000)
}
空接口
空接口是接口類型的特殊形式,空接口沒有任何方法。因此任何類型都無須實現,從實現的角度來看。任何值都滿足這個接口的需求。因此空接口類型可以保存任何值,也可以從中取值。
保存值
package main
import (
"fmt"
)
func main() {
// 將各種數據類型的值保存到空接口
var any interface{}
any = 1
fmt.Println(any)
any = 0.99
fmt.Println(any)
any = "Hello Gp"
fmt.Println(any)
any = true
fmt.Println(any)
any = []string{}
fmt.Println(any)
any = [...]string{}
fmt.Println(any)
any = map[string]int{}
fmt.Println(any)
}
空接口的應用
空接口作為函數的參數
package main
import "fmt"
func show(v interface{}) {
fmt.Println(v)
}
func main() {
show("江子牙")
show(520)
show(true)
}
空接口作為map的value
package main
import "fmt"
func main() {
a := map[string]interface{}{
"name": "江子牙",
"age": 21,
"isLogin": true,
}
fmt.Println(a)
}
接口和類型之間的轉換
go 語言中使用接口斷言(type assertions) 將接口轉換成另一外一個接口,也可以將接口轉另外的類型。使用非常頻繁。
類型斷言
格式:
t := i.(T)
- i:代表接口變量
- T:代表轉換的目標類型
- t:轉換后的變量
如果i沒有完全實現T接口的方法,這個語句會觸發宕機,觸發宕機不是很友好,因為有另一種保守寫法。
t, ok := i.(T)
這種寫法的好處就是如果發送接口未實現時,將會返回一個布爾值false,即ok的值,而且t的類型為0。正常實現時,ok為true。
接口轉化為其他接口
例子:
package main
import (
"fmt"
)
// 定義飛行動物接口
type Flyer interface {
Fly(s string)
}
// 定義爬行動物接口
type Walker interface {
Walk(s string)
}
// 定義小鳥類結構體
type Bird struct {
}
// 定義小豬類結構體
type Pig struct {
}
// 為小鳥類實現飛的技能和走路的技能
func (b *Bird) Fly(s string) {
fmt.Println("小鳥飛行:", s)
}
func (b *Bird) Walk(s string) {
fmt.Println("小鳥走路:", s)
}
// 為小豬實現走路的技能
func (p *Pig) Walk(s string) {
fmt.Println("死豬跑不動嗎:", s)
}
func main() {
// 先創建一個字典來存放接口的信息。
animals := map[string]interface{}{
"bird": new(Bird),
"pig": new(Pig),
}
fmt.Println(animals)
// 循環map
for name, obj := range animals {
// 類型斷言:判斷obj是爬行動物還是飛行動物
f, isFly := obj.(Flyer)
w, isWalk := obj.(Walker)
fmt.Printf("name:%s\tisFlyer:%v\tisWalker:%v\n", name, isFly, isWalk)
// 類型判斷
if isFly {
f.Fly("100米")
}
if isWalk {
w.Walk("1000步")
}
}
}
接口轉化為類型
package main
import (
"fmt"
)
// 定義爬行動物接口
type Walker interface {
Walk(s string)
}
// 定義小豬類結構體
type Pig struct {
}
// 為小豬實現走路的技能
func (p *Pig) Walk(s string) {
fmt.Println("死豬跑不動嗎:", s)
}
func main() {
// 將接口轉為其他類型:可以實現將接口轉為普通的指針類型
// 實例化出一個小豬結構體
p1 := new(Pig)
// 聲明一個類型為小豬爬行類w接口
var w Walker = p1
fmt.Println(w)
fmt.Printf("%T\n", w)
fmt.Printf("%T\n", p1)
// 將w接口轉為*Pig類型
p2 := w.(*Pig)
fmt.Printf("p1 = %p\np2 = %p", p1, p2)
}
執行結果:
&{}
*main.Pig
*main.Pig
p1 = 0x5861b0
p2 = 0x5861b0
判斷接口中變量的類型
判斷基本類型
package main
import (
"fmt"
)
func printType(i interface{}) {
switch i.(type) {
case int:
fmt.Println("int類型")
case string:
fmt.Println("string類型")
case bool:
fmt.Println("bool類型")
default:
fmt.Println("不知名類型")
}
}
func main() {
// 使用類型分支判斷基本類型
printType("str")
printType(1)
printType(true)
printType([]string{})
}
執行結果:
string類型
int類型
bool類型
不知名類型
判斷接口類型
package main
import (
"fmt"
)
// 刷臉的接口
type useFace interface {
Face(string)
}
// 刷人民幣值為100的接口
type useOneHundred interface {
OneHundred(string)
}
// 支付寶方式結構體
type Alipy struct {
}
// 現金方式方式結構體
type Cash struct {
}
// 為支付寶提供人臉支付的方法
func (a Alipy) Face(s string) {
fmt.Println(s)
}
// 為現金支付提供支付100元的方法
func (c Cash) OneHundred(s string) {
fmt.Println(s)
}
func printPay(payMethod interface{}) {
var pay = payMethod
// 判斷接口類型
switch payMethod.(type) {
case useFace:
face := pay.(*Alipy)
face.Face("刷臉")
case useOneHundred:
cash := pay.(*Cash)
cash.OneHundred("支付100毛爺爺")
}
}
func main() {
//使用類型分支判斷接口類型
printPay(new(Alipy))
printPay(new(Cash))
}
執行結果:
刷臉
支付100毛爺爺
