---
# System prepended metadata

title: 'Day12 Go神奇的介面與斷言(Interface, assertion)'

---

# 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`這一行所使用。  
放裡面或放外面都不行，  
但這種特殊用法也只在這裡會用到。    
所以可以自己寫個方法來判斷所傳入的型別。  