Golang Switch .(type) 判斷型別,runtime執行不同型別

寫一個系統程式時,碰到一個問題如下: 因為同一個Struct中的一個變數,我們姑且叫它變數一,有可能是type A 或是 type B,根據type A或 type B會執行各自不同的功能,但因為同一個struct中還有許多其他相同的共用變數及method,因此不想將該物件拆成(事實上,以筆者的系統也不能拆成兩個)不同的struct type,因此用了interface{}來作為變數一的type,因此能順利的將不管是type A 或type B assign給變數一(從乾淨程式碼的角度來看,這也許不是個好做法,對於後來程式維護來講將提高難度),但能賦值後,於執行到該變數一時,要呼叫它的功能或變數,卻會發現因為type=interface{},而無法呼叫(錯誤訊息: type interface{} has no field or method XXXX)。

後來用了Switch .(type)這樣的作法來處理,因此這邊用一個簡單的例子來說明如何使用switch .(type)來解決: 於runtime時,動態(Dynamic)判斷物件是屬於哪個type,並進而用該type的方法來執行功能。

Switch .(type)

Switch .(type)能判斷所指定的變數是屬於哪個type,例如是Int,string,或甚至是我們自己定義的Type A,而能判斷是屬於哪個type後,並能給一個變數=所指定的變數並cast成該變數真正的type (ex: c:= object.(type),利用.(type)達成cast,此時c 就是實際的object被assign的type,而不再是Interface{} type的變數),接著利用switch判斷該變數屬於哪個type( ex: case *typeA: …. case *typeB:….),就能呼叫該type原本的變數及方法囉(ex: c.Variable)。

Demo Code :

下面一個簡單的例子來展示上面所說的,有一個變數InputB 被設為interface{}型別,可能被assign為InputBType1 或 InputBType2兩種之一(這邊Demo用rand來產生不確定性),最後用switch .(type)的方式來判斷使屬於哪個type,並使用該type的方法及印出該type的變數值。(注意:case中使用的是指標(pointer, *)來做判斷)。

package main

import (
	"fmt"
	"math/rand"
	"time"
)

type DynamicTypeInput struct {
	InputA int
	InputB interface{}
}

type InputBType1 struct {
	VarInt int
}

func (T *InputBType1) ShowVar(){
 	fmt.Println(T.VarInt) 
}


type InputBType2 struct {
	VarString string
}
func (T *InputBType2) ShowVar(){
 	fmt.Println(T.VarString) 
}


func main() {

	var object1 = new(DynamicTypeInput)

	object1.InputB = GenerateType()

	//object1.InputB.VarInt  // error message:(type interface{} has no field or method VarInt))

	//how to get and use InputB's variable and method

	switch c := object1.InputB.(type) {
	case *InputBType1:
        c.ShowVar()	
        fmt.Println("In Type1,variable value=", c.VarInt)
	case *InputBType2:
        c.ShowVar()
		fmt.Println("In Type2,variable value=", c.VarString)
	default:
		fmt.Println(c)
	}

}

func GenerateType() interface{} {
	rand.Seed(time.Now().UnixNano())
	var typeChoice = rand.Intn(2)
	fmt.Println("generate number is :", typeChoice)
	if typeChoice == 1 {
		return &InputBType1{VarInt: 1000}
	} else {
		return &InputBType2{VarString: "OneThousand"}
	}

}

附註:

為什麼能將interface{} type重新變成被賦值的type? 這是因為golang介面的處理方式,當一個物件被當成一個介面Assign後,其實還保留了物件原本的type的資訊,可以透過reflect或是這邊所展示的switch .(type)等方式來取得。