# Day12 Go神奇的介面與斷言(Interface, assertion) ## 神奇的介面 #### 【Interface 介面】 介面是什麼?`介面就是窗口、接口`(不是某種電子支付) 上面有講跟沒講一樣,因為這個字詞超級無敵宇宙抽象的。 生活上會用到跟介面相關的詞語:介面活性劑、這介面好醜、... 通俗來講介面就是一個`可溝通隔板`,將兩者完全不同的東西區分開來,彼此卻又可以有關聯、互相影響。 常常看到的`API`應用程式介面,其中的I就是介面:意思是開放了功能出來,這些功能與內部程式碼實作的部分完全無關,你不知道也可以用,有點`黑箱`的意思(電視機內部、CPU內部對我來講都是黑箱,但是能用就對了)。 在Go語言中,`interface`就是`任意值`,可以放任何東西進去, 沒錯,就像四次元百寶袋那樣。 ~~Google:『既然是介面,就盡量用,不要吝嗇嘛!』~~ https://play.golang.org/p/rBLJrC5J-dX ```go func main() { var a interface{} a = 123 fmt.Println(a, reflect.TypeOf(a)) a = "hi" fmt.Println(a, reflect.TypeOf(a)) a = true fmt.Println(a, reflect.TypeOf(a)) } /* result: 123 int hi string true bool */ ``` 嗯,我看到了啥... 我餵給他什麼他都能吃欸! 我餵給他`int`,他就變成`int`型態 我餵給他`string`,他就變成`string`型態 我餵給他`bool`,他就變成`bool`型態 自動轉了型態...? 那`interface`到底是什麼型態? ```go func main() { var a interface{} fmt.Println(a, reflect.TypeOf(a)) } /* result: <nil> <nil> */ ``` 居然是 `nil` 不過這也正常,因為什麼東西都還沒給,所以還沒實體化。 --- 除了任意值之外,`interface`還有一個廣泛的用法 可以定義`func的名字`在`interface`之中。 你可能會問:嗯?這樣可以做什麼呢? 可以方便容易地`抽層`啊!不是外面賣東西抽成的抽層, 而是讓你的程式架構`多一層`,能讓程式更抽象化、更具有程式彈性! > 譬如說,我平常刷牙都是用`牙膏`, > 我可以在`interface`中定義:**刷起來會發生什麼事**這個`func` > 但等哪天我刷牙不想用`牙膏`這個介面了, > 因為覺得牙膏實在太不給力,我想我可以換一個介面活性劑來使用, > 我總可以換成`洗碗精`、或`漂白水`這個介面活性劑來刷牙吧! > > 沒錯,那屆時改程式只需**實作洗碗精的func `刷起來會發生什麼事`**, > 譬如說**中毒**。 > > 我完全不用改動到`刷牙`程式,就算用`漂白水`也可以繼續刷牙啊! 這裡舉原本養貓,一夕之間卻改養狗的範例: ```go // 一開始看時,覺得寫interface之後再來引用宣告, // 根本多此一舉,拿石頭砸自己的腳 type Animal interface { eat() } type Cat struct { catName string } func (c Cat) eat() { fmt.Println(c.catName, "開動哩") } func main() { var cat1 Animal = Cat{"肥貓一號"} cat1.eat() var cat2 Animal = Cat{"笨貓二號"} cat2.eat() } /* result: 肥貓一號 開動哩 笨貓二號 開動哩 */ ``` > 哪天突然想不開,覺得貓太肥太醜,很不可愛 > 想要棄貓養狗的時候 > 只需額外實作`Dog.eat() 的 func`、創個`Dog物件` > 程式本體架構仍維持著高可讀性 ```go var dog1 Animal = Dog{"開心狗一號"} dog1.eat() ``` https://play.golang.org/p/F-QDduaOAz7 ## 斷言 這裡的斷言不是打斷別人言論, 而是`我敢斷言`的斷言,`宣稱`的意思。 #### 【Type assertions】 > A type assertion provides access to an interface value's underlying concrete value. `型別斷言`提供存取介面實體化的值的方法 試著把`interface`轉成該行別,並把值取出,用法有些特殊 ```go func main() { var hello interface{} = "hello" helloStr := hello.(string) fmt.Println(helloStr) } /* result: hello */ ``` 如果給錯型別會引發`Panic` ```go func main() { var hello interface{} = "hello" helloInt := hello.(int) fmt.Println(helloInt) } // panic: interface conversion: interface {} is string, not int ``` 所以想在不知道型別的狀況下取出介面的型別值, 可以加上`ok`此一變數來驗證 動作是否成功 ```go func main() { var hello interface{} = "hello" helloInt, ok := hello.(int) fmt.Println(helloInt, ok) } /* result: 0 false */ ``` https://play.golang.org/p/MwPrTo5nOoh #### 【Switch Type】 可以用這個特殊的方法來檢驗一個物件的型別。 https://play.golang.org/p/FwJOD5Tvhs3 ```go func main(){ whatType(123) } func whatType(i interface{}) { switch v := i.(type) { case int: fmt.Println("int") case string: fmt.Println("string") case bool: fmt.Println("bool") case nil: fmt.Println("nil") default: fmt.Println("unknown", v) } } ``` 為什麼說特殊呢? 因為這 `i.(type)` 只能搭配`switch`這一行所使用。 放裡面或放外面都不行, 但這種特殊用法也只在這裡會用到。 所以可以自己寫個方法來判斷所傳入的型別。