iT邦幫忙

2021 iThome 鐵人賽

DAY 8
0
Modern Web

fmt.Println("從零開始的Golang生活")系列 第 8

Day8 Function and Interface

Function

func (basedMethod) funcName(parameters)(returnType) {
}

Go的func命名是採用駝峰式大小寫,譬如:FindTheJob

字首大寫代表對外暴露,是個global function。

字首小寫則只能在相同package中使用。

無回傳值的func

func Variable() {
...
}

package main

import (
	"fmt"
)

func main() {
	a()
}

func a() {
	fmt.Println("Hello World")
}

運行後可得以下結果

Hello World

有回傳值的func

回傳一個變數,要先定義回傳的變數型態

func Variable() ReturnType {
...
}

package main

import (
	"fmt"
)

func main() {
	fmt.Println(b())
}

func a() {
	fmt.Println("Hello World")
}

func b() int {
	return 1 + 2
}

運行後可得以下結果

3

有回傳值且預先宣告變數名稱的func

func Variable() (Variable ReturnType) {
...
}

等同於在func中做了var variable type這件事

所以func內的variable可以直接取用

func d() (e string) {
	e = "I am e!"
	return e
}

多個回傳值的func

func Variable() (ReturnType, ReturnType) {
...
}

package main

import (
	"fmt"
)

func main() {
	fmt.Println(c())
}
a
func a() {
	fmt.Println("Hello World")
}

func b() int {
	return 1 + 2
}

func c() (int, string) {
	return 1 + 2, "參"
}

運行後可得結果

3 參

Struct中的Func

Struct搭配Func

func (S Struct) Variable() {
...
}

package main

import (
	"fmt"
)

type Bag struct {
	Wallet string
	Pen string
}

func main() {
	var bag Bag
	bag.Pen = "BlackPen"
	bag.show()
}

func a() {
	fmt.Println("Hello World")
}

func b() int {
	return 1 + 2
}

func c() (int, string) {
	return 1 + 2, "參"
}

func (Bag *Bag) show() {
	pen := Bag.Pen
	fmt.Println(pen) 
}

運行後可得以下結果

BlackPen

這方法讓你的物件Bag 動了起來,

他不在只是個物件,他現在會show時展示筆的資訊。

Interface

Go的Interface類型是對其他類型行為的抽象和概括,因為Interface不會和特定的實現細節綁定一起透過該種抽象方式可以讓我們對對象更加靈活和更具有適應能力。

Go中的interface可以是任意值。

package main

import (
	"fmt"
)

type Area interface {
    Calculate() float64
}

type Rect struct {
    width  float64
    height float64
}

func (r Rect) Calculate() float64 {
    return r.height * r.width
}

func main() {
    var a Area
    fmt.Printf("(%T, %v) \n", a, a)
    a= Rect{3, 5}
    fmt.Printf("(%T, %v) \n", a, a)
    fmt.Println(a.Calculate())
}

運行後可得以下結果

(<nil>, <nil>) 
(main.Rect, {3 5}) 
15

我們可以得知

  • Area這個interface剛實體化時type與value皆為nil
  • 之後賦予其struct後type為Rect
  • 之後便才可以使用Calculate(),若在賦予struct前執行Calculate()便會抱錯。

下面我們再看一個生動的例子,讓大家更加了解interface

package main

import (
	"fmt"
)

type Animal interface {
	eat()
}

type Cat struct {
	catName string
}

func (c Cat) eat() {
	fmt.Println(c.catName, "Eat!!")
}

func main() {
	var cat1 Animal = Cat{"Black Cat"}
	cat1.eat()

	var cat2 Animal = Cat{"White Cat"}
	cat2.eat()
}

運行後可得以下結果

Black Cat Eat!!
White Cat Eat!!

Type assertions

型別斷言 提供存取介面實體化值的方法

試著將interface轉成該型別,並把值取出

package main

import (
	"fmt"
)

func main() {
	var hello interface{} = "hello"
	helloStr := hello.(string)
	fmt.Println(helloStr)
}

運行後可得以下結果

hello

如果給錯型別會引發Panic

package main

import (
	"fmt"
)

func main() {
	var hello interface{} = "hello"
	helloStr := hello.(string)
	fmt.Println(helloStr)
}

運行後會得以下結果

panic: interface conversion: interface {} is string, not int

goroutine 1 [running]:
main.main()
	/tmp/sandbox1366433049/prog.go:9 +0x2e

所以想在不知道型別的狀況下取出介面的型別值,

可以加上ok此一變數來驗證 動作是否成功

package main

import (
	"fmt"
)

func main() {
	var hello interface{} = "hello"
	helloInt, ok := hello.(int)
	fmt.Println(helloInt, ok)
}

運行後可得以下結果

0 false

Switch Type

可以用這個特殊的方法來檢驗一個物件的型別。

package main

import (
	"fmt"
)

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)
	}
}

運行後可得以下結果

int

i.(type)只能夠搭配switch使用,因此特殊。

Polymorphism

當一個type實作了interface後,該type所建立的變數除了屬於原本type外,也屬於該interface的type,一個變數同時符合多個型別就被稱為Polymorphism 多型。

package main

import (
	"fmt"
)

type Answer interface {
    getSalary() int
}

type Question struct {
    a, b int
}

func (q Question) Add() int {
    return q.a + q.b
}

type Student struct {
    firstName, lastName string
    question              Question
}

func main() {
    flynn := Student{
        firstName: "Flynn",
        lastName:  "Sun",
        question: Question{
            30, 20,
        },
    }

    fmt.Println("Student's Answer", flynn.question.Add())
}

舉例來說,這裡先定義了 Salaried 這個 interface type,接著 Salary 這個 struct type 實作了 Salaried 中定義的 method signatures,因此 Salary 這個 struct type 也同時符合了 Salaried interface type,這樣的行為稱作 polymorphism。

舉例來說,這裡我們

  1. 定義了Answer這個Interface type
  2. 接下來 Question這個struct type實作了Answer中的method signatures
  3. 所以Question的struct type同時也符合了Answer interface type

這行為被稱作polymorphism

Standard Example

綜合以上好處,interface在Golnag的標準庫當中是非常常用到的,像是定義檔案的讀寫的Reader, Writer interface等

type Reader interface {
	Read(p []byte) (n int, err error)
}

type Writer interface {
	Write(p []byte) (n int, err error)
}

兩者皆利用了os.File實現其interface,已完成檔案的讀與寫

Summary

Function與Interface基本上已經是Go開發中不可或缺的元素了,尤其是Function!

但這邊介紹的用法也只是冰山一角,希望大家入門後可以再透過實作與練習再去更深入它。


上一篇
Day7 Map and Struct
下一篇
Day9 Goroutine
系列文
fmt.Println("從零開始的Golang生活")30

尚未有邦友留言

立即登入留言