寫一個系統程式時,碰到一個問題如下: 因為同一個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)等方式來取得。