2016-05-09 4 views
2

Ich habe eine Struktur, die wie folgt aussieht:Wie kann ich Schnittstellen verwenden, um mehr als 1 Strukturtyp zu ermöglichen, um Code brauchbarer zu machen?

type BetaKey struct { 
    Id   int64 `json:"-"` 
    IssuerId  int64 `json:"-"   db:"issuer_id"` 
    SubjectEmail string `json:"-"   db:"subject_email"` 
    IssuedTime int64 `json:"-"   db:"issued_time"` 
    ExpiryTime int64 `json:"expiryTime" db:"expiry_time"` 
    Betakey  string `json:"accessKey"` 
    Description string `json:"description"` 
} 

Und im gleichen Paket habe ich eine Funktion, die ein Stück BetaKey zurückgibt:

func buildResults(query string) ([]BetaKey, error) { 
    results := []BetaKey{} 
    rows, err := sql.DB.Queryx(query) 
    if err != nil { 
     return results, err 
    } 
    defer rows.Close() 
    for rows.Next() { 
     var bk BetaKey 
     err := rows.StructScan(&bk) 
     if err != nil { 
      return results, err 
     } 
     results = append(results, bk) 
    } 
    err = rows.Err() 
    if err != nil { 
     return results, err 
    } 
    return results, nil 
} 

Ist es möglich für mich, diese Funktion zu überschreiben so dass es eine Abfragezeichenfolge aber auch eine Art von BetaKey als interface{} einnimmt und ein Stück interface{} zurückgibt, damit ich den Code anstelle des Kopierens verwenden kann, das dieses in jedes Paket einfügt, weil es buchstäblich das selbe ist aber der einzige Unterschied ist das Name von t er struct das ändert sich.

Ist das möglich? Und ist das auch ratsam? Wenn nicht, warum?

+0

Ja, es ist möglich. Lernen Sie etwas über das Nachdenken über Golang. – Apin

+0

Ich weiß, wie man den Namen der Struktur mit Reflektion bekommt. Und ich weiß, dass ich die Schnittstelle mit Syntax (Typ) konvertieren kann. Aber ich weiß immer noch nicht, wie ich die obige Funktion umschreiben soll. – Alex

Antwort

1

Generics so etwas zu implementieren verwendet werden könnte, aber Go unterstützt keine Generika. Um zu tun, was Sie in Go wollen, müssen Sie Reflexion verwenden.

Sie können Ihre Funktion ändern, um 1 zusätzlichen Parameter zu nehmen, z. B. reflect.Type, der den Typ der Werte angibt, in die die einzelnen Zeilen geladen werden sollen.

Dann können Sie reflect.New() verwenden, um einen neuen Wert dieses Typs zu erstellen und einen Zeiger darauf zu erhalten. Sie können Value.Interface() verwenden, um den Zeigerwert als einen Typ interface{} aus dem Wert reflect.Value zu erhalten. Diese interface{} Umhüllung des Zeigers kann nun an Rows.StructScan() übergeben werden.

Und wenn Sie das Ergebnis in Scheiben schneiden wollen Nicht-Zeigerwert zu halten, können Sie reflect.Indirect() verwenden, um den spitzen Wert zu erhalten (und andere reflect.Interface() die Struktur Wert als interface{} zu extrahieren).

Beispielcode:

func buildResults(query string, t reflect.Type) ([]interface{}, error) { 
    results := []interface{}{} 
    rows, err := sql.DB.Queryx(query) 
    if err != nil { 
     return results, err 
    } 
    defer rows.Close() 
    for rows.Next() { 
     val := reflect.New(t) 
     err := rows.StructScan(val.Interface()) 
     if err != nil { 
      return results, err 
     } 
     i_ := reflect.Indirect(val) 
     result = append(result, i_.Interface()) 
    } 
    err = rows.Err() 
    if err != nil { 
     return results, err 
    } 
    return results, nil 
} 

Das Herz ist der for Block:

val := reflect.New(t)     // A pointer to a new value (of type t) 
err := rows.StructScan(val.Interface()) // Pass the pointer to StructScan 
if err != nil { 
    return results, err 
} 
i_ := reflect.Indirect(val)    // Dereference the pointer 
result = append(result, i_.Interface()) // And add the non-pointer to the result slice 

Hier ist, wie Sie es testen können:

type BetaKey struct { 
    Id string 
    Name string 
} 

type AlphaKey struct { 
    Id  string 
    Source string 
} 

r, err := buildResults("", reflect.TypeOf(AlphaKey{})) 
fmt.Printf("%T %+v %v\n", r[0], r, err) 

r, err = buildResults("", reflect.TypeOf(BetaKey{})) 
fmt.Printf("%T %+v %v\n", r[0], r, err) 

Ausgang:

main.AlphaKey [{Id:aa Source:asource} {Id:aa Source:asource} {Id:aa Source:asource}] <nil> 
main.BetaKey [{Id:aa Name:aname} {Id:aa Name:aname} {Id:aa Name:aname}] <nil> 

Versuchen Sie es auf der Go Playground.

Anmerkungen:

Die obige Lösung wird einen Wert vom Typ der []interface{} deren Elemente zurückzubringen wird von statischen Typ interface{} und ihre dynamischen Typ wird derjenige durch den reflect.Type Argument angegeben sein.So zum Beispiel, wenn Sie es mit Typ nennen:

bt := reflect.TypeOf(BetaKey{}) 

Die Werte im Ergebnis Slice dynamischen Typen haben BetaKey so können Sie sicher type assert sie wie folgt aus:

results, err := buildResults("some query", bt) 
if err == nil { 
    for _, v := range results { 
     key := v.(BetaKey) 
     // key is of type BetaKey, you may use it like so 
    } 
} else { 
    // handle error 
} 

Um mehr über Reflexion zu erfahren, lesen sie den Blog-Eintrag:

The Go Blog: The Laws of Reflection

+0

Vielen Dank, mein Herr! Ich sehe, dass dir StarCraft in deinem Profil gefällt. Du hast nicht zufällig Iccup gespielt? ^^ – Alex

+0

Wenn ich json.Marshal das Ergebnis wollte, müsste ich den Interface {} Typ zu BetaKey mit. (Typ) Syntax zuerst konvertieren? Oder ist es möglich, json.Marshal die Schnittstelle? – Alex

+0

@Alex In Starcraft (Broodwar) war ich ein "BGH-Typ" (spielte Big Game Hunters), kein Iccup, aber heutzutage, wenn ich spiele, spiele ich natürlich nur StarCraft II. – icza

1

Ich schreibe ein kleines Beispiel mit json, anstelle von SQL-Zeilen. Sie können versuchen, aus diesem Code zu entwickeln:

package main 

import (
    "fmt" 
    "reflect" 
    "encoding/json" 
) 

type A struct { 
    AField int `json:"a"` 
} 

type B struct { 
    BField string `json:"b"` 
} 

func build(str string, typ reflect.Type) interface{} { 
    results := reflect.MakeSlice(reflect.SliceOf(typ), 0, 10) 
    for i:=0; i < 5; i++ { 
     res := reflect.New(typ) 
     json.Unmarshal([]byte(str), res.Interface()) 
     results = reflect.Append(results, res.Elem()) 
    } 
    return results.Interface(); 
} 

func main() { 
    a := build("{ \"a\": 15 }", reflect.TypeOf(&A{})) 
    fmt.Printf("%T : %V\n", a, a) 
    b := build("{ \"b\": \"my string\" }", reflect.TypeOf(&B{})) 
    fmt.Printf("%T : %V\n", b, b) 
} 

Golang Playground

+0

Was passiert, wenn ich die Länge und Kapazität der Scheibe vorher nicht kenne? Ich bin es gewohnt, nur Slices zu erstellen und Sachen dynamisch hinzuzufügen, ohne etwas über Länge und Kapazität zu sagen. – Alex

+0

kein Problem, du kannst eine beliebige Zahl setzen. Es wächst automatisch. – Apin

Verwandte Themen