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