Ik heb een interface Model
, die is geïmplementeerd door struct Person
.
Om een modelinstantie te krijgen, heb ik de volgende hulpfuncties:
func newModel(c string) Model {
switch c {
case "person":
return newPerson()
}
return nil
}
func newPerson() *Person {
return &Person{}
}
Met de bovenstaande aanpak kan ik een correct getypte Person-instantie retourneren (kan later gemakkelijk nieuwe modellen toevoegen met dezelfde aanpak).
Toen ik iets soortgelijks probeerde te doen voor het retourneren van een deel van de modellen, krijg ik een foutmelding. Code:
func newModels(c string) []Model {
switch c {
case "person":
return newPersons()
}
return nil
}
func newPersons() *[]Person {
var models []Person
return &models
}
Go klaagt met: cannot use newPersons() (type []Person) as type []Model in return argument
Mijn doel is om een segment terug te sturen van welk modeltype dan ook dat wordt aangevraagd (of het nu gaat om []Person
, []FutureModel
, []Terminator2000
, wij). Wat mis ik en hoe kan ik zo’n oplossing goed implementeren?
Antwoord 1, autoriteit 100%
Dit lijkt erg op een vraag die ik zojuist heb beantwoord: https://stackoverflow.com/a/12990540/727643
Het korte antwoord is dat je gelijk hebt. Een plakje structs is niet gelijk aan een plakje van een interface die de struct implementeert.
Een []Person
en een []Model
hebben verschillende geheugenlay-outs. Dit komt omdat de typen waarvan ze plakjes zijn, verschillende geheugenlay-outs hebben. Een Model
is een interfacewaarde, wat betekent dat het in het geheugen twee woorden groot is. Het ene woord voor de typegegevens, het andere voor de gegevens. Een Person
is een struct waarvan de grootte afhangt van de velden die het bevat. Om van een []Person
naar een []Model
te converteren, moet je de array doorlopen en een typeconversie uitvoeren voor elk element.
Aangezien deze conversie een O(n)-bewerking is en zou resulteren in het maken van een nieuwe slice, weigert Go dit impliciet te doen. Je kunt het expliciet doen met de volgende code.
models := make([]Model, len(persons))
for i, v := range persons {
models[i] = Model(v)
}
return models
En zoals dskinner opmerkte, wil je hoogstwaarschijnlijk een segment met verwijzingen en niet een verwijzing naar een segment. Een aanwijzer naar een segment is normaal gesproken niet nodig.
*[]Person // pointer to slice
[]*Person // slice of pointers
Antwoord 2, autoriteit 7%
Misschien is dit een probleem met uw retourtype *[]Person
, waar het eigenlijk []*Person
zou moeten zijn, dus om te verwijzen naar elke index van het segment een verwijzing naar een Person
, en waar een slice []
op zichzelf een verwijzing naar een array is.
Bekijk het volgende voorbeeld:
package main
import (
"fmt"
)
type Model interface {
Name() string
}
type Person struct {}
func (p *Person) Name() string {
return "Me"
}
func NewPersons() (models []*Person) {
return models
}
func main() {
var p Model
p = new(Person)
fmt.Println(p.Name())
arr := NewPersons()
arr = append(arr, new(Person))
fmt.Println(arr[0].Name())
}
Antwoord 3, autoriteit 6%
Omdat Stephen de vraag al heeft beantwoord en je een beginner bent, leg ik de nadruk op het geven van adviezen.
Een betere manier om met de interfaces van go te werken is om geen constructor terug te laten keren
de interface zoals je misschien gewend bent van andere talen, zoals java, maar om te hebben
een constructor voor elk object afzonderlijk, aangezien ze de interface impliciet implementeren.
In plaats van
newModel(type string) Model { ... }
je zou moeten doen
newPerson() *Person { ... }
newPolitician() *Politician { ... }
met Person
en Politician
die beide de methoden van Model
implementeren.
U kunt nog steeds Person
of Politician
gebruiken overal waar een Model
wordt geaccepteerd, maar u kunt ook andere interfaces implementeren.
Met uw methode bent u beperkt tot Model
totdat u een handmatige conversie uitvoert naar
een ander type interface.
Stel dat ik een Person
heb die de methode Walk()
implementeert en een Model
ShowOff()
implementeert , zou het volgende niet direct werken:
newModel("person").ShowOff()
newModel("person").Walk() // Does not compile, Model has no method Walk
Dit zou echter:
newPerson().ShowOff()
newPerson().Walk()
Antwoord 4, autoriteit 3%
Zoals anderen al hebben geantwoord, is []T een apart type. Ik zou alleen willen toevoegen dat een eenvoudig hulpprogramma kan worden gebruikt om ze generiek te converteren.
import "reflect"
// Convert a slice or array of a specific type to array of interface{}
func ToIntf(s interface{}) []interface{} {
v := reflect.ValueOf(s)
// There is no need to check, we want to panic if it's not slice or array
intf := make([]interface{}, v.Len())
for i := 0; i < v.Len(); i++ {
intf[i] = v.Index(i).Interface()
}
return intf
}
Nu kun je het als volgt gebruiken:
ToIntf([]int{1,2,3})
Antwoord 5, autoriteit 2%
Typen T en []T zijn verschillende typen en onderscheiden zijn ook hun methoden, zelfs als ze aan dezelfde interface voldoen. IOW, elk type dat aan het model voldoet, moet alle methoden van het model zelf implementeren – de methode-ontvanger kan slechts één specifiek type zijn.
Antwoord 6, autoriteit 2%
Zelfs als de implementatie van Go dit toestond, is het helaas ondeugdelijk: je kunt geen []Person
toewijzen aan een variabele van het type []Model
omdat een []Model
heeft verschillende mogelijkheden. Stel dat we bijvoorbeeld ook Animal
hebben die Model
implementeert:
var people []Person = ...
var models []Model = people // not allowed in real Go
models[0] = Animal{..} // ???
var person Person = people[0] // !!!
Als we regel 2 toestaan, dan zou regel 3 ook moeten werken omdat models
perfect een Animal
kunnen opslaan. En regel 4 zou nog steeds moeten werken omdat people
Person
s opslaat. Maar dan krijgen we een variabele van het type Person
met een Animal
!
Java staat feitelijk het equivalent van regel 2 toe, en het wordt algemeen als een fout beschouwd. (De fout wordt tijdens runtime gedetecteerd; regel 3 zou een ArrayStoreException
opleveren.)