Go 語言 接口(Interface)

What is Interface type in Go ?

GoLang官網language specification文檔對interface type的概念說明如下:

An interface type specifies a method set called its interface.
A variable of interface type can store a value of any type with a method set that is any superset of the interface.
Such a type is said to implement the interface. The value of an uninitialized variable of interface type is nil.

Go 語言提供了另外一種數據類型即接口(interface),它把所有的具有共性的方法定義在一起,這些方法只有函數簽名,沒有具體的實現代碼(類似于Java中的抽象函數),任何其他類型只要實現了接口中定義好的這些方法,那么就說 這個類型實現(implement)了這個接口

接口的通用定義方式如下

/* 定義接口 */
type interface_name interface {
   method_name1 [return_type]
   method_name2 [return_type]
   method_name3 [return_type]
   ...
   method_namen [return_type]}

/* 定義xxx數據結構類型 */
type struct_name xxx

/* 實現接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
   /* 方法實現 */}
    ...
func (struct_name_variable struct_name) method_namen() [return_type] {
   /* 方法實現*/}

上面使用type聲明一個名為 interface_name 的接口,interface類型是可以定義為變量的,比如

var  name  interface_name

接口在Go語言中是引用類型,因此在上面定義的接口變量中,name是一個指針

我們依照上面的接口通用定義方式,定義以下實例

//定義電話接口
type Phone interface {
   call()
}
//自定義結構體
type Nokia struct {

}
//實現接口方法
func (nokia Nokia)call()  {
   fmt.Println("I am Nokia, I can call you!")
}

func main() {
   var  phone Phone
   phone = new(Nokia)
   phone.call()
}

call()是Phone接口定義好的一個方法,然后由Nokia實現了接口中這個方法,所以Nokia 實現了 Phone接口。

Interface“多態”特性實例
interface{}類型的變量,可以使用任何類型的值來賦值

func main() {
   var a interface{}  = 123
   var b interface{}  = "abc"
   var c interface{}  = 1.23
   fmt.Println(a,b,c)
}
-----output----
123 abc 1.23

在Go語言中自帶的標準Packages中,有很多地方利用 interface 來處理未知數據類型,比如我們常用的fmt包,以fmt.Println為例,它的函數簽名格式如下

// Println formats using the default formats for its operands and writes to standard output.
// Spaces are always added between operands and a newline is appended.
// It returns the number of bytes written and any write error encountered.
func Println(a ...interface{}) (n int, err error) {
   return Fprintln(os.Stdout, a...)
}

fmt包的Println函數需要傳入interface類型的可變長參數,該函數在實現底層的打印行為時,要求傳入的可變長參數實現了fmt包中定義的Stringer接口。
Stringer接口類型描述如下:

// Stringer is implemented by any value that has a String method,
// which defines the ``native'' format for that value.
// The String method is used to print values passed as an operand
// to any format that accepts a string or to an unformatted printer
// such as Print.
type Stringer interface {
   String() string
}

所以,自定義類型想要調用fmt.Printf()做格式化打印,那只需實現Stringer接口就行。

舉例說明:自定義輸出類型格式
定義一個map集合,并循環輸出map的元素

type PersonInfo struct {
   ID string
   Name string
   address string
}
func main() {
   //創建集合
   var myMap map[string] PersonInfo
   //初始化集合
   myMap = make(map[string]PersonInfo)
   //向map中添加元素
   myMap ["1"] = PersonInfo{"1","zhangsan","shanghai"}
   myMap ["2"] = PersonInfo{"2","wangwu","beijing"}
   myMap ["3"] = PersonInfo{"3","lisi","tianjin"}
   //循環輸出
   for num,person := range myMap{
      fmt.Printf("%v: %v\n",num,person)
   }
}
----------output------------
1: {1 zhangsan shanghai}
2: {2 wangwu beijing}
3: {3 lisi tianjin}

現在我們要自定義map集合輸出的格式,比如輸出第一個元素:1: {1&zhangsan&shanghai},因此 PersonInfo 需要實現Stringer接口

//自定義類型需要實現Stringer接口
func (pInfo PersonInfo)String()string  {
   return fmt.Sprintf("%v&%v&%v",pInfo.ID,pInfo.Name,pInfo.address)
}

最終自定義格式化輸出為:

1: 1&zhangsan&shanghai
2: 2&wangwu&beijing
3: 3&lisi&tianjin

接口嵌入
Go語言的接口對嵌入支持的非常好,接口可以嵌入其他的接口,效果就像在接口中 直接添加被嵌入接口的方法一樣。

type HavingFoot interface {
   Foot()
}
type HavingEye interface {
   Eye()
}

type HuMan interface {
   HavingEye
   HavingFoot
}

HuMan接口嵌入了 HavingFoot 接口與 HavingEye 接口,就相當于HuMan接口包含了HavingFoot 接口與 HavingEye 接口中所有的方法。如果要實現HuMan接口,就要定義 Foot() 與 Eye() 方法。

類型猜測
如果有兩個類型都實現了同一接口,那么這兩個類型變量A,B都可以賦值給這個接口類型的變量C(類似于Java繼承中的向上傳遞),然后這個接口類型的變量C,可以去查找是類型變量A還是B給它賦值的,查詢通用格式如下:

value,ok := obj.(struct)

struct:需要猜測的類型A或B value:返回類型變量A或B
ok:查詢結果 obj:接口類型的變量C

類型猜測實例

//定義電話接口
type Phone interface {
   call()
}
//自定義結構體
type Nokia struct {

}
type Iphone struct {

}

//實現接口方法
func (nokia Nokia)call()  {
   fmt.Println("I am Nokia, I can call you!")
}

func (iphone  Iphone)call()  {
   fmt.Println("I am Iphone, I can call you!")
}

func main() {
   //將兩個子類賦值給接口
   var phone1 Phone = Nokia{}
   var phone2 Phone = Iphone{}
   //判斷phone1是不是Nokia賦值給它的
   if values,ok := phone1.(Nokia);ok {
      values.call()
   }
   //判斷phone2是不是Nokia賦值給它的
   if values,ok := phone2.(Iphone);ok {
      values.call()
   }
}

推薦閱讀:https://studygolang.com/articles/2652

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容