Go 語言編程 — 高級數(shù)據(jù)類型 — Interface、多態(tài)、Duck Typing 與泛式編程(google)
目錄

文章目錄
目錄
Golang 的接口
Interface 實例存儲的是實現(xiàn)者的值
如何判斷某個 Interface 實例的實際類型
Empty Interface
Interface 與多態(tài)
Interface 與 Duck Typing
Interface 與泛型編程
Golang 的接口
Golang 的 Interface 是一種派生數(shù)據(jù)類型,使用 type 和 interface 關(guān)鍵字來聲明,它定義了一組方法(Method)的簽名(函數(shù)名、形參列表、返回值)。
Go 不是一種典型的 OOP 編程語言,所以 Golang 在語法上不支持類和繼承的概念。沒有繼承是否就無法擁有多態(tài)特性呢?Golang 通過 Interface 實現(xiàn)了多態(tài)。
從語法上看,Interface 定義了若干個 Method,這些 Method 只有函數(shù)簽名,沒有具體的函數(shù)體。如果某個數(shù)據(jù)類型實現(xiàn)了 Interface 中定義的所有 Method,則稱該數(shù)據(jù)類型實現(xiàn)了這個 Interface。這是典型的面向?qū)ο笏枷搿?/p>
格式:
/* 定義接口 */ type interface_name interface { method_name1 [return_type] ... method_namen [return_type] } /* 定義結(jié)構(gòu)體 */ type struct_name struct { /* variables */ } /* 實現(xiàn)接口方法 */ func (struct_name_variable struct_name) method_name1() [return_type] { /* 方法實現(xiàn) */ } ... func (struct_name_variable struct_name) method_namen() [return_type] { /* 方法實現(xiàn)*/ }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
可見,相對于基本數(shù)據(jù)類型所具備的實際操作意義,Golang Interface 的價值內(nèi)涵更傾向于一種 Duck Typing(鴨子類型)編程思想的體現(xiàn),是一種編程方式的實現(xiàn)機制。
Interface 實例存儲的是實現(xiàn)者的值
示例:
type I interface { Get() int Set(int) } type S struct { Age int } // S 實現(xiàn)了 I 的兩個方法(Set、Get),就說 S 是 I 的實現(xiàn)者。 func(s S) Get() int { return s.Age } func(s *S) Set(age int) { s.Age = age } func f(i I){ i.Set(10) fmt.Println(i.Get()) } func main() { s := S{} // 若結(jié)構(gòu)體實例實現(xiàn)了某個 interface,那么就可以將其傳遞到接受這個 interface 為實參的函數(shù)。 // 執(zhí)行 f(&s) 就做完了一次 interface 類型的使用。 f(&s) }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
上例中,如果有多種數(shù)據(jù)類型實現(xiàn)了某個 Interface,這些數(shù)據(jù)類型的實例對象都可以直接使用 function f 中的代碼實現(xiàn),這就是泛式編程的思想,也稱為 Duck Typing。
不難看出 Interface 實例中存儲的值,其實就是實現(xiàn)了這個 Interface 的數(shù)據(jù)類型的實例的值。Golang 會自動進(jìn)行 Interface 的檢查,并在運行時隱式的完成從其他數(shù)據(jù)類型到該 Interface 類型的自動轉(zhuǎn)換。
如何判斷某個 Interface 實例的實際類型
因為 Interface 實例存儲的是實現(xiàn)者的值,即 Interface 實例實際上就是式實現(xiàn)者數(shù)據(jù)類型的實例。而且,一個 Interface 可以被多種不同數(shù)據(jù)類型多實現(xiàn)。所以,我們在編程的過程中,會遇見需要判斷一個 Interface 實例的實際數(shù)據(jù)類型的情況。
Golang 中可以使用 Type assertions(類型斷言)語句來進(jìn)行判斷,e.g. value, ok := em.(T),其中:
em 是一個 Interface 實例。
T 代表要進(jìn)行斷言的數(shù)據(jù)類型
value 則是 Interface 的實現(xiàn)者。
ok 是 bool 類型,表示斷言的結(jié)果。
示例:
if t, ok := i.(*S); ok { fmt.Println("s implements I", t) }
1
2
3
如果需要區(qū)分多種類型,可以使用 switch 斷言,更簡單直接,這種斷言方式只能在 switch 語句中使用。
示例:
switch t := i.(type) { case *S: fmt.Println("i store *S", t) case *R: fmt.Println("i store *R", t) }
1
2
3
4
5
6
Empty Interface
不包含任何 Method 的 Interface,即:interface{},稱之為 Empty Interface。
根據(jù)前文的定義:一個數(shù)據(jù)類型如果實現(xiàn)了一個 Interface 的所有 Methods 就稱該數(shù)據(jù)類型實現(xiàn)了這個 Interface。那么,Empty Interface 由于沒有包含任何 Method,所以可以認(rèn)為所有的數(shù)據(jù)類型都實現(xiàn)了 interface{}。
也就是說,如果一個函數(shù)的形參為 interface{} 類型,那么這個形參可以接受任何數(shù)據(jù)類型的實例對象作為實參數(shù)。
示例:
func doSomething(i interface{}){ ... }
1
2
3
需要注意的是,上述例子中,雖然形參 i 可以接受任何數(shù)據(jù)類型的實參,但并不代表著 v 就是任何數(shù)據(jù)類型,在函數(shù)體中,v 僅僅是 interface{} 類型。
另外,需要注意 slice interface{} 類型的特殊之處,例如:
func printAll(vals []interface{}) { //1 for _, val := range vals { fmt.Println(val) } } func main(){ names := []string{"stanley", "david", "oscar"} printAll(names) }
1
2
3
4
5
6
7
8
9
10
編譯上述代碼會得到錯誤:cannot use names (type []string) as type []interface {} in argument to printAll.
說明 Golang 并沒有隱式的將 slice []string 數(shù)據(jù)類型轉(zhuǎn)換為 slice []interface,因為 interface{} 會占用 2Byte 的存儲空間,一個 Byte 存儲自身的 Methods 數(shù)據(jù),一個 Byte 存儲指向其存儲值的指針,也就是 Interface 實例存儲的實現(xiàn)者的值。
因而 slice []interface{} 其長度是固定的 N2,但是 []T 的長度是 Nsizeof(T),兩種 slice 實際存儲值的大小是有區(qū)別的,所以就無法完成自動數(shù)據(jù)類型轉(zhuǎn)換了。但是我們可以手動地進(jìn)行轉(zhuǎn)換,即:顯示的完成 Slice 長度匹配,例如:
var dataSlice []int = foo() var interfaceSlice []interface{} = make([]interface{}, len(dataSlice)) for i, d := range dataSlice { interfaceSlice[i] = d }
1
2
3
4
5
6
7
Interface 與多態(tài)
在 Java、C++ 等靜態(tài)類型語言中,在定義變量必須要顯示聲明其數(shù)據(jù)類型。但有些時候,如果我們希望它可以寬松一點,比如說我們用父類指針或引用去調(diào)用一個方法,在執(zhí)行的時候,能夠根據(jù)子類的類型去執(zhí)行子類當(dāng)中的方法。也就是說實現(xiàn)我們用相同的調(diào)用方式調(diào)出了不同結(jié)果或者是功能的情況,這種情況就叫做多態(tài)。
實現(xiàn)多態(tài)的技術(shù)稱為:動態(tài)綁定(Dynamic Binding),是指在執(zhí)行期間判斷所引用對象的實際類型,根據(jù)其實際的類型調(diào)用其相應(yīng)的方法。
現(xiàn)實中,關(guān)于多態(tài)的例子不勝枚舉。比方說按下 F1 鍵這個動作,在 Flash 界面下就是彈出 AS 3 幫助文檔;在 Word 下就是彈出 Word 幫助;在 Windows 下就是彈出 Windows 幫助和支持。同一個事件發(fā)生在不同的對象上會產(chǎn)生不同的結(jié)果。
再舉個非常經(jīng)典的例子,比如說貓、狗和人都是哺乳動物。這三個類都有一個 say 方法,但結(jié)果是:貓是喵喵叫,狗是汪汪叫,人類則是說話。
class Mammal { public void say() { System.out.println("do nothing") } } class Cat extends Mammal{ public void say() { System.out.println("meow"); } } class Dog extends Mammal{ public void say() { System.out.println("woof"); } } class Human extends Mammal{ public void say() { System.out.println("speak"); } } class Main { public static void main(String[] args) { List
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
這三個類都是 Mammal 的子類,雖然我們用的是父類的引用來調(diào)用的方法,但是它可以自動根據(jù)子類的類型調(diào)用對應(yīng)不同子類當(dāng)中的方法,得到的結(jié)果是:
speak woof meow
1
2
3
簡而言之,多態(tài)的作用同樣是消除了 Method 與(父、子)數(shù)據(jù)類型之間的耦合關(guān)系,在父類當(dāng)中定義方法,在子類中可以創(chuàng)建不同的實現(xiàn),這就是抽象類和抽象方法的來源。
我們可以把 Mammal 做成一個抽象類,聲明 say 是一個抽象方法。抽象類是不能直接創(chuàng)建實例的,只能創(chuàng)建子類的實例,并且抽象方法也不用實現(xiàn),只需要標(biāo)記好參數(shù)和返回就行了,具體的實現(xiàn)都在子類當(dāng)中進(jìn)行:
abstract class Mammal { abstract void say(); }
1
2
3
Java Interface 的本質(zhì)就是一個抽象類,與抽象類的區(qū)別在于 Interface 是只有抽象方法的抽象類。
interface Mammal { void say(); }
1
2
3
Interface 的好處是很明顯的,我們可以用 Interface(“父類”)的實例來調(diào)用所有實現(xiàn)了這個接口的類(“子類”)。如此的,Interface 和它的具體實現(xiàn)是一種要寬泛許多的繼承關(guān)系,大大增加了靈活性。
而 Golang Interface 則做得更加徹底,因為 Golang 不是一門面向?qū)ο缶幊陶Z言,所以 Golang 完全沒有了 Interface 本身和實現(xiàn)者之間的繼承關(guān)系。只要 Interface 中定義的 Method 能對應(yīng)的上,那么就可以認(rèn)為這個數(shù)據(jù)類型實現(xiàn)了這個接口。
type Dog struct{} type Cat struct{} type Human struct{} func (d Dog) Say() { fmt.Println("woof") } func (c Cat) Say() { fmt.Println("meow") } func (h Human) Say() { fmt.Println("speak") } func main() { var m Mammal m = Dog{} m.Say() m = Cat{} m.Say() m = Human{} m.Say() }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Interface 與 Duck Typing
對于 Duck Typing(鴨子類型)有一個簡單的說明:“If it walks like a duck and it quacks like a duck, then it must be a duck”。
對于計算機而言,當(dāng)看到一只鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那么這只鳥就可以被稱為鴨子。
計算機以此來判斷 it 是否可以用于特定的目的。即:一個東西究竟是不是鴨子,取決于它能不能滿足鴨子的工作。
在面向?qū)ο缶幊陶Z言中,當(dāng)某個地方(e.g. 函數(shù)的形參)需要符合某個條件的變量(e.g. 要求這個變量實現(xiàn)了某種方法)時,什么是判斷這個變量是否 “符合條件” 的標(biāo)準(zhǔn)呢?如果這個標(biāo)準(zhǔn)是:這個變量的數(shù)據(jù)類型是否實現(xiàn)了這個要求的方法(并不要求顯式地聲明),那么這種語言的類型系統(tǒng)就可以稱為 Duck Typing。
可見,Duck Typing 是一種編程風(fēng)格,在這種風(fēng)格中,一個對象有效的語義,不是由繼承自特定的類,而是由 “當(dāng)前方法和屬性的集合” 來決定。
簡而言之,Duck Typing 將對象的語義和其繼承的類進(jìn)行了解偶,程序判斷一個對象是聲明并不是通過它的數(shù)據(jù)類型定義來判斷的,而是判斷它是否滿足了某些特定的方法和屬性定義,以此加大了編程的靈活度 —— 雖然我不繼承自某個類,但我依然可以完成某些工作。
Duck Typing 多見于動態(tài)語言,例如:Python 等,因為動態(tài)語言不需要在定義一個變量的時候就聲明其不可更改的數(shù)據(jù)類型,即:變量類型可被隨意更改,所以在函數(shù)傳遞中支持靈活的類型傳遞,繼而實現(xiàn) “任務(wù)” 函數(shù)的定義。
Duck Typing 在靜態(tài)語言中比較罕見,Golang Interface 就是 Duck Typing 的一種實現(xiàn),因為 Golang 不是一種面向?qū)ο缶幊陶Z言,同樣沒有類型一致的約束。
示例:
#!/usr/local/bin/python3 # coding=utf8 # 使用的對象和方法 class PsyDuck(object): def gaga(self): print("這是可達(dá)鴨") # 使用的對象和方法 class DoningdDuck(object): def gaga(self): print("這是唐老鴨") # 被調(diào)用的 “任務(wù)” 函數(shù) def duckSay(func): return func.gaga() # 限制調(diào)用方式 if __name__ != '__main__': print("must __main__") # 實例化對象 duck = PsyDuck() person = DoningdDuck() # 調(diào)用函數(shù) duckSay(duck) duckSay(person)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
示例:
package main import "fmt" // 定義一個鴨子接口。 // Golang 中的接口是一組方法的集合,可以理解為抽象的類型。它提供了一種非侵入式的接口。任何類型,只要實現(xiàn)了該接口中方法集,那么就屬于這個類型。 type Duck interface { Gaga() } // 假設(shè)現(xiàn)在有一個可達(dá)鴨類型。 type PsyDuck struct{} // 可達(dá)鴨聲明方法,滿足鴨子會嘎嘎叫的特性。 func (pd PsyDuck) Gaga() { fmt.Println("this is PsyDuck") } // 假設(shè)現(xiàn)在有一個唐老鴨類型。 type DonaldDuck struct{} // 唐老鴨聲明方法,滿足鴨子會嘎嘎叫的特性。 func (dd DonaldDuck) Gaga() { fmt.Println("this is DoningdDuck") } // 要調(diào)用的 “任務(wù)” 函數(shù),負(fù)責(zé)執(zhí)行鴨子能做的事情,注意這里的參數(shù),有類型限制為 Duck 接口。 func DuckSay(d Duck) { d.Gaga() } func main() { fmt.Println("duck typing") //實例化對象 var pd PsyDuck //可達(dá)鴨類型 var dd DonaldDuck //唐老鴨類型 //調(diào)用方法 //pd.Gaga() // 因為可達(dá)鴨實現(xiàn)了所有鴨子的函數(shù),所以可以這么用。 //dd.Gaga() // 因為唐老鴨實現(xiàn)了所有鴨子的函數(shù),所以可以這么用。 DuckSay(pd) // 因為可達(dá)鴨實現(xiàn)了所有鴨子的函數(shù),所以可以這么用。 DuckSay(dd) // 因為唐老鴨實現(xiàn)了所有鴨子的函數(shù),所以可以這么用。 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Interface 與泛型編程
In the simplest definition, generic programming is a style of computer programming in which algorithm are written in terms of types to-be-specified-later that are then instantiated when needed for specific types provided as parameters.
– From Wikipedia.
簡而言之,泛式編程,就是編寫的代碼不針對某一特定類型(比如適用于 Int,不適用于 String)生效,而是面向大部分?jǐn)?shù)據(jù)類型都可以工作的。
示例:
#include
1
2
3
4
5
6
7
8
9
10
上述的 sort 函數(shù)就是一個泛式編程的例子,它可以處理 Int 和 String 類型的變量。
總所周知,Golang 目前是沒有泛式編程的,但可以通過 Interface 特性來實現(xiàn),雖然不盡完美。Golang 中也提供了 sort 函數(shù):
package sort // A type, typically a collection, that satisfies sort.Interface can be // sorted by the routines in this package. The methods require that the // elements of the collection be enumerated by an integer index. type Interface interface { // Len is the number of elements in the collection. Len() int // Less reports whether the element with // index i should sort before the element with index j. Less(i, j int) bool // Swap swaps the elements with indexes i and j. Swap(i, j int) } ... // Sort sorts data. // It makes one call to data.Len to determine n, and O(n*log(n)) calls to // data.Less and data.Swap. The sort is not guaranteed to be stable. func Sort(data Interface) { // Switch to heapsort if depth of 2*ceil(lg(n+1)) is reached. n := data.Len() maxDepth := 0 for i := n; i > 0; i >>= 1 { maxDepth++ } maxDepth *= 2 quickSort(data, 0, n, maxDepth) }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
可以看見,package sort 中定義了一個 Interface,包含了 Len、Less、Swap 方法,這個 Interface 類型作為 Sort 函數(shù)的形參。如果我們要使用 Sort,就只需要定義這個 Interface 的實現(xiàn)者就可以了。
package main import ( "fmt" "sort" ) type Person struct { Name string Age int } func (p Person) String() string { return fmt.Sprintf("%s: %d", p.Name, p.Age) } // ByAge implements sort.Interface for []Person based on // the Age field. type ByAge []Person func (a ByAge) Len() int { return len(a) } func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age } func main() { people := []Person{ {"Bob", 31}, {"John", 42}, {"Michael", 17}, {"Jenny", 26}, } fmt.Println(people) sort.Sort(ByAge(people)) fmt.Println(people) }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
AI Go 數(shù)據(jù)結(jié)構(gòu)
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。