Go的interface
比較酷,一個是傳統OOP
用來抽象化行為的interface
,另一個是「泛用型別」,統合在一篇內一起講好了
介面 interface
是只有方法宣告,但缺乏方法實作的型別。介面定義了一組未實作的行為,將程式抽象化,建立較低耦合的系統。
若看習慣java
的interface
,會比較不習慣Go不使用implements
,而是直接將功能拿去寫能就直接實現介面。
定義一個名為Animal
的介面,有Eat
與Run
兩種方法。
type Animal interface {
Eat()
Run()
}
建立Monkey
並實做出Interface
內的方法
package main
import "fmt"
type Animal interface {
Eat()
Run()
}
type Monkey struct {
Name string
}
func (m *Monkey) Eat() {
fmt.Printf("%s is eating bananas.\n", m.Name)
}
func (m *Monkey) Run() {
fmt.Printf("%s is running.\n", m.Name)
}
func main() {
monkey := Monkey{Name:"BenLee"}
monkey.Run()
monkey.Eat()
}
output:
BenLee is running.
BenLee is eating bananas.
Animal
的介面,內涵Eat()
與Run()
兩種動物會有的行為Cat struct
,並實作了Eat()
與Run()
兩個function
,對Go來說實作了Interface
的功能就跟implements
的概念一樣。pointer reciever
的方式來實作,不然會導致淺複製行為,不會更改到同一struct
若宣告一介面,此介面是儲存nil
type Animal interface {
Eat()
Run()
}
func main() {
var animal Animal
fmt.Println(animal)
}
輸出:
nil
將有實做該Interface
的strcut
指派給nil Interface
,該Interface
則會在底層儲存該struct
實例
func main() {
monkey := Monkey{Name: "BenLee"}
var animal Animal
animal = &monkey
fmt.Println(animal)
}
輸出:
&{BenLee}
多型的概念是相同的函式傳入不同物件,會引發不同的行為,在Go裡可以透過interface
來實現。
package main
import "fmt"
//首先有個Animal的界面
type Animal interface {
Eat()
Run()
}
//建立兩個struct,Monkey、Hamster,並實做interface
type Monkey struct {
Name string
}
func (m *Monkey) Eat() {
fmt.Printf("%s is eating bananas.\n", m.Name)
}
func (m *Monkey) Run() {
fmt.Printf("%s is running.\n", m.Name)
}
type Hamster struct {
Name string
}
func (m *Hamster) Eat() {
fmt.Printf("%s is eating sunflower seeds.\n", m.Name)
}
func (m *Hamster) Run() {
fmt.Printf("%s is running.\n", m.Name)
}
//寫ShowEat函式,觀賞傳入的動物吃飯ˊˇˋ,傳入的是interface
//程式會依照此interface的型別來運作。
func ShowEat(animal Animal) {
animal.Eat()
}
func main() {
monkey := Monkey{Name: "BenLee"}
hamster := Hamster{Name: "Thomas"}
ShowEat(&monkey)
ShowEat(&hamster)
}
輸出:
BenLee is eating bananas.
Thomas is eating sunflower seeds.
interface
如果在後面加上兩個大括號,可以把它當作一種type
使用,範例如下:
func main() {
var a interface{}
var b interface{}
var c interface{}
a = 10
b = 12.5
c = true
fmt.Printf("a = %d, type = %T\n", a, a)
fmt.Printf("b = %f, type = %T\n", b, b)
fmt.Printf("c = %t, type = %T\n", c, c)
}
output:
a = 10, type = int
b = 12.500000, type = float64
c = true, type = bool
看起來很好用對吧?這只是表面而已,interface
其實還是個interface
而已,看看下面這個例子
func main() {
var a interface{} = 10
var b interface{} = 12
fmt.Println(a+b)
}
輸出:
invalid operation: operator + not defined on a (variable of type interface{})
再換個例子
func main() {
var imonkey interface{}
imonkey = &Monkey{"BenLee"}
fmt.Println(imonkey) // &{BenLee}
fmt.Println(imonkey.Name) // Error
}
輸出:
a.Name undefined (type interface{} has no field or method Name)
因此用interface{}接受到的參數或建立的變數,還需要經過assert
才能進行使用。
a := i.(type)
a
: 目標變數i
: interface的變數type
: 欲轉換成的型態這邊直接把上面例子修正好試試看
func main() {
var ia interface{} = 10
var ib interface{} = 12
a := ia.(int)
b := ib.(int)
fmt.Println(a + b)
}
output:
22
func main() {
var imonkey interface{}
imonkey = &Monkey{"BenLee"}
monkey := imonkey.(*Monkey)
fmt.Println(monkey.Name)
}
output:
BenLee
當型態太多時,為了避免轉錯,也可以加入檢測機制a, ok := i.(*type)
a
: 目標變數ok
: 確認型別是否正確,正確則true,錯誤則falsei
: interface的變數type
: 欲轉換成的型態給一個成功的例子
func main() {
var imonkey interface{}
imonkey = &Monkey{"BenLee"}
// test true
monkey, ok := imonkey.(*Monkey)
if ok == true {
fmt.Println("Assert success.\nName:", monkey.Name)
} else {
fmt.Println("Assert false")
}
}
output:
Assert success.
Name: BenLee
與一個失敗的例子,這邊要將Monkey
型態的interface
轉成Hamster
,因此ok = false
func main() {
var imonkey interface{}
imonkey = &Monkey{"BenLee"}
// test true
monkey, ok := imonkey.(*Hamster)
if ok == true {
fmt.Println("Assert success. Name:", monkey.Name)
} else {
fmt.Println("Assert false")
}
}
output:
Assert false
當然執行時不可能預先知道每個interface
的型態並去寫轉換對吧?因此要先預設好可能會傳入的變數型態去幫助assert
。以上面例子為例,假設我可能傳入Monkey
、Hamster
好了
func autoAssert(inter interface{}){
switch inter.(type) {
case *Monkey:
animal := inter.(*Monkey)
fmt.Println(animal.Name)
animal.Eat()
case *Hamster:
animal := inter.(*Hamster)
fmt.Println(animal.Name)
animal.Eat()
}
}
func main() {
animals := [...]Animal{
&Monkey{"BenLee"},
&Hamster{"Thomas"},
}
for _,v := range animals{
autoAssert(v)
}
}
output:
BenLee
BenLee is eating bananas.
Thomas
Thomas is eating sunflower seeds.
透過switch
的方式看interface
屬於什麼型別,並去做assert
。
關於最後Type Assertion的部分可能描寫得不是很好,自己還沒發現較為實用的例子,也不清楚實際開發時遇到怎樣的狀況比較需要使用,之後若有遇到再回來補充
day15 - 介面(續)
https://ithelp.ithome.com.tw/articles/10218401
[Golang] 程式設計教學:用介面 (Interface) 實踐繼承和多型
https://opensourcedoc.com/golang-programming/interface/
Golang - 深入理解 interface 常見用法
https://blog.kennycoder.io/2020/02/03/Golang-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3interface%E5%B8%B8%E8%A6%8B%E7%94%A8%E6%B3%95/
Golang Interface
https://ithelp.ithome.com.tw/articles/10204662
Interface & OOP 就說你是鴨子! 你就是要呱呱叫
https://ithelp.ithome.com.tw/articles/10215623