go語言的接口


go語言的接口

簡介

  • 接口是雙方規定的一種合作協議,接口實現者不需要關心接口會被怎樣使用,調用者不需要關心接口的實現細節。接口是一種類型。也是一種抽象結構。不會暴露所含數據的格式、類型及結構。比如只要一台洗衣機有洗衣服的功能和甩干的功能,我們就稱為洗衣機,不關心屬性(數據),只關心行為(方法)。
  • 接口和其他動態語言的鴨子模型有密切關系。比如說pythonjavascript。任何類型,只要實現了該接口中的方法集,那么就屬於這個類型。
  • 每個接口由數個方法組成。

接口的定義

格式:

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可以直接調用GaGaYouYong。他並不知道這兩個方法內部怎么實現的。

值類型接收者和指針型接收者實現接口

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。正常實現時,oktrue

接口轉化為其他接口

例子:

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毛爺爺


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM