看到介面這兩個字,我們一定會聯想到面向介面程式設計。說白了就是介面指定執行物件的具體行為,也就是介面表示讓執行物件具體應該做什麼,所以,普遍意義上講,介面是抽象的,而實際執行行為,則是具象的。
介面(interface)的定義
在Go lang中,介面是一組方法簽名,當型別為介面中的所有方法提供定義時,它被稱為實現介面。和麵向介面的思想非常類似,介面指定了型別應該具有的方法,型別決定了到底該怎麼實現這些方法:
/* 定義介面 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
/* 定義結構體 */
type struct_name struct {
/* variables */
}
/* 實現介面方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法實現 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法實現*/
}
具體實現方式:
package main
import (
"fmt"
)
type Phone interface {
call()
}
type Android struct {
}
func (android Android) call() {
fmt.Println("I am Android")
}
type Ios struct {
}
func (ios Ios) call() {
fmt.Println("I am Ios")
}
func main() {
var phone Phone
phone = new(Android)
phone.call()
phone = new(Ios)
phone.call()
}
程式返回:
I am Android
I am Ios
是的,現在我們可以結構體、函式、以及介面三箭齊發了,這裡首先定義好手機介面,並且指定call()方法,意思是我在抽象層面擁有一個手機,手機應該具有打電話的功能。
隨後分別定義結構體和函式(也是方法),分別具現化的實現介面的指定行為,精神上大家是一樣的,但肉體上,一個是安卓,另一個則是蘋果。
Go lang中,介面可以被任意的物件實現,同樣地,一個物件也可以實現任意多個介面,任意的型別都實現了空介面(interface{}),也就是包含0個method的interface。
誠然,如果單獨使用結構體,我們也可以,實現類似多型的結構:
package main
import "fmt"
type Human struct {
name string
age int
phone string
}
type Student struct {
Human //匿名欄位
school string
loan float32
}
type Employee struct {
Human //匿名欄位
company string
money float32
} //Human實現Sayhi方法
func (h Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
} //Human實現Sing方法
func (h Human) Sing(lyrics string) {
fmt.Println("。。。。。。。。", lyrics)
} //Employee重寫Human的SayHi方法
func (e Employee) SayHi() {
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
e.company, e.phone) //Yes you can split into 2 lines here.
}
可以單獨為結構體定義方法,但如果介面參與邏輯:
type Men interface {
SayHi()
Sing(lyrics string)
}
func main() {
mike := Student{Human{"Mike", 10, "1"}, "MIT", 0.00}
paul := Student{Human{"Paul", 20, "2"}, "Harvard", 100}
sam := Employee{Human{"Sam", 30, "3"}, "Golang Inc.", 1000}
Tom := Employee{Human{"Tom", 40, "4"}, "Things Ltd.", 5000}
//定義Men型別的變數i
var i Men
//i能儲存Student
i = mike
fmt.Println("This is Mike, a Student:")
i.SayHi()
i.Sing("song")
//i也能儲存Employee
i = Tom
fmt.Println("This is Tom, an Employee:")
i.SayHi()
i.Sing("song")
//定義了slice Men
fmt.Println("Let's use a slice of Men and see what happens")
x := make([]Men, 3)
//T這三個都是不同型別的元素,但是他們實現了同一個介面
x[0], x[1], x[2] = paul, sam, mike
for _, value := range x {
value.SayHi()
}
}
程式返回:
This is Mike, a Student:
Hi, I am Mike you can call me on 1
。。。。。。。。 song
This is Tom, an Employee:
Hi, I am Tom, I work at Things Ltd.. Call me on 4
。。。。。。。。 song
Let's use a slice of Men and see what happens
Hi, I am Paul you can call me on 2
Hi, I am Sam, I work at Golang Inc.. Call me on 3
Hi, I am Mike you can call me on 1
由此可見,介面的出現,把本來不相關的結構體型別以抽象的形式結合了起來,不同的型別實現內容不同的共性方法。
也就是說,Men介面型別的變數i,那麼i裡面可以存Human、Student或者Employee值,所以i是抽象的,而Human、Student或者Employee就是i的具象化操作。
介面指定函式引數
介面不僅僅可以指定無參方法,也可以指定具體的引數,讓函式接受各種型別的引數:
package main
import "fmt"
type Human interface {
Len()
}
type Student interface {
Human
}
type Test struct {
}
func (h *Test) Len() {
fmt.Println("10個")
}
func main() {
var s Student
s = new(Test)
s.Len()
}
程式返回:
10個
這裡使用介面巢狀的形式,Human介面定義了Len方法,結構體Test實現了所有的Len介面方法,當結構體s中呼叫Test結構體的時候,s就相當於Python中的繼承,s繼承了Test,因此,s可以不用重寫所有的Human介面中的方法,因為父構造器已經實現了介面。
鴨子型別(ducktyping)
什麼是鴨子型別?當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子。
所謂遠看山有色,近聽水無聲,春去花還在,人來鳥不驚,意象上來講,一個事物究竟是不是某一種型別,取決於它具不具備這個型別的特性,這就是鴨子型別的本質。
所以鴨子型別主要描述事物的外部行為而非內部構造,在物件導向的程式語言中,比如Python中,一個物件有效的語義,不是由繼承自特定的類或實現特定的介面,而是由"當前方法和屬性的集合"決定。
編寫test.py檔案:
class PsyDuck():
def gaga(self):
print("這是可達鴨")
# 使用的物件和方法
class DoningdDuck():
def gaga(self):
print("這是唐老鴨")
# 被呼叫的函式
def duckSay(func):
return func.gaga()
# 限制呼叫方式
if __name__ != '__main__':
print("must __main__")
if __name__ == "__main__":
# 例項化物件
duck = PsyDuck()
person = DoningdDuck()
# 呼叫函式
duckSay(duck)
duckSay(person)
程式返回:
這是可達鴨
這是唐老鴨
所以到底是什麼鴨子不重要,重要的是呼叫了誰的例項。
再來看看go lang的手筆:
package main
import "fmt"
//定義一個鴨子介面
//Go 介面是一組方法的集合,可以理解為抽象的型別。它提供了一種非侵入式的介面。任何型別,只要實現了該介面中方法集,那麼就屬於這個型別。
type Duck interface {
Gaga()
}
//假設現在有一個可達鴨型別
type PsyDuck struct{}
//可達鴨宣告方法-滿足鴨子會嘎嘎叫的特性
func (pd PsyDuck) Gaga() {
fmt.Println("this is PsyDuck")
}
//假設現在有一個唐老鴨型別
type DonaldDuck struct{}
//唐老鴨宣告方法-滿足鴨子會嘎嘎叫的特性
func (dd DonaldDuck) Gaga() {
fmt.Println("this is DoningdDuck")
}
//要呼叫的函式 - 負責執行鴨子能做的事情,注意這裡的引數,有型別限制為Duck介面
func DuckSay(d Duck) {
d.Gaga()
}
func main() {
//提示開始列印
fmt.Println("duck typing")
//例項化物件
var pd PsyDuck //可達鴨型別
var dd DonaldDuck //唐老鴨型別
//呼叫方法
DuckSay(pd) //因為可達鴨實現了所有鴨子的函式,所以可以這麼用
DuckSay(dd) //因為唐老鴨實現了所有鴨子的函式,所以可以這麼用
}
程式返回:
duck typing
this is PsyDuck
this is DoningdDuck
這裡首先定義抽象的鴨子介面,指定gaga方法,不同的結構體:可達鴨、唐老鴨分別繫結並且實現了鴨子介面的方法,然後宣告一個呼叫函式,在執行的時候,將結構體變數傳遞給呼叫函式,動態地實現了不同型別的方法。
結語
所謂介面(interface)的抽象性,就是從表面看到本質,從片面看到整體,然後抽出那些穩定的、共有的特性。平時我們會考慮程式碼的重用性,元件的複用性,同一個功能對不同場景的複用性,有了複用的能力,就能夠用更少的開發去滿足更多場景的同類需求問題。從而能夠從一個具體的需求,看到一類的需求,看到衍生的相關的需求,甚至再對需求進行分類,看到更高層面的需求。進而才能夠系統性解決同類的需求而不是就事論事點對點解決問題。
所以,總的來說,介面的極致就是抽象,而抽象的極致,則是格局,介面,可以更好的幫我們擴大程式視野的格局。