Ich bin mit der Tatsache vertraut, dass in Go Schnittstellen eher Funktionalität als Daten definieren. Sie fügen eine Reihe von Methoden in eine Schnittstelle ein, können jedoch keine Felder angeben, die für irgendetwas erforderlich wären, das diese Schnittstelle implementiert.
Beispielsweise:
// Interface
type Giver interface {
Give() int64
}
// One implementation
type FiveGiver struct {}
func (fg *FiveGiver) Give() int64 {
return 5
}
// Another implementation
type VarGiver struct {
number int64
}
func (vg *VarGiver) Give() int64 {
return vg.number
}
Jetzt können wir die Schnittstelle und ihre Implementierungen verwenden:
// A function that uses the interface
func GetSomething(aGiver Giver) {
fmt.Println("The Giver gives: ", aGiver.Give())
}
// Bring it all together
func main() {
fg := &FiveGiver{}
vg := &VarGiver{3}
GetSomething(fg)
GetSomething(vg)
}
/*
Resulting output:
5
3
*/
Was Sie jetzt nicht tun können, ist ungefähr so:
type Person interface {
Name string
Age int64
}
type Bob struct implements Person { // Not Go syntax!
...
}
func PrintName(aPerson Person) {
fmt.Println("Person's name is: ", aPerson.Name)
}
func main() {
b := &Bob{"Bob", 23}
PrintName(b)
}
Nachdem ich jedoch mit Schnittstellen und eingebetteten Strukturen herumgespielt habe, habe ich einen Weg gefunden, dies auf folgende Weise zu tun:
type PersonProvider interface {
GetPerson() *Person
}
type Person struct {
Name string
Age int64
}
func (p *Person) GetPerson() *Person {
return p
}
type Bob struct {
FavoriteNumber int64
Person
}
Aufgrund der eingebetteten Struktur hat Bob alles, was Person hat. Es implementiert auch die PersonProvider-Schnittstelle, sodass wir Bob an Funktionen übergeben können, die für die Verwendung dieser Schnittstelle ausgelegt sind.
func DoBirthday(pp PersonProvider) {
pers := pp.GetPerson()
pers.Age += 1
}
func SayHi(pp PersonProvider) {
fmt.Printf("Hello, %v!\r", pp.GetPerson().Name)
}
func main() {
b := &Bob{
5,
Person{"Bob", 23},
}
DoBirthday(b)
SayHi(b)
fmt.Printf("You're %v years old now!", b.Age)
}
Hier ist ein Go-Spielplatz , der den obigen Code demonstriert.
Mit dieser Methode kann ich eine Schnittstelle erstellen, die eher Daten als Verhalten definiert und die von jeder Struktur implementiert werden kann, indem diese Daten einfach eingebettet werden. Sie können Funktionen definieren, die explizit mit diesen eingebetteten Daten interagieren und sich der Natur der äußeren Struktur nicht bewusst sind. Und alles wird beim Kompilieren überprüft! (Die einzige Möglichkeit, vermasseln könnte, dass ich sehen kann, würde die Schnittstelle werden die Einbettung PersonProvider
in Bob
, sondern als ein Beton Person
. Es wäre kompilieren und zur Laufzeit fehl.)
Hier ist meine Frage: Ist das ein ordentlicher Trick oder sollte ich es anders machen?
Antworten:
Es ist definitiv ein ordentlicher Trick. Durch das Anzeigen von Zeigern steht jedoch weiterhin ein direkter Zugriff auf Daten zur Verfügung, sodass Sie nur eine begrenzte zusätzliche Flexibilität für zukünftige Änderungen erhalten. Außerdem müssen Sie bei Go-Konventionen nicht immer eine Abstraktion vor Ihre Datenattribute stellen .
Wenn ich diese Dinge zusammen nehme, tendiere ich für einen bestimmten Anwendungsfall zum einen oder anderen Extrem: entweder a) erstelle einfach ein öffentliches Attribut (ggf. durch Einbetten) und gebe konkrete Typen weiter oder b) wenn es so aussieht, als würde es die Daten offenlegen Wenn Sie später Probleme verursachen, legen Sie einen Getter / Setter für eine robustere Abstraktion frei.
Sie werden dies pro Attribut abwägen. Wenn beispielsweise einige Daten implementierungsspezifisch sind oder Sie erwarten, dass die Darstellungen aus einem anderen Grund geändert werden, möchten Sie das Attribut wahrscheinlich nicht direkt verfügbar machen, während andere Datenattribute möglicherweise stabil genug sind, um sie öffentlich zu machen.
Durch das Ausblenden von Eigenschaften hinter Gettern und Setzern erhalten Sie zusätzliche Flexibilität, um später abwärtskompatible Änderungen vorzunehmen. Angenommen, Sie möchten eines Tages
Person
nicht nur ein einzelnes "Namens" -Feld, sondern auch das erste / mittlere / letzte / Präfix speichern. Wenn Sie über MethodenName() string
und verfügenSetName(string)
, können Sie vorhandene Benutzer derPerson
Benutzeroberfläche bei Laune halten, während Sie neue feinkörnigere Methoden hinzufügen. Oder Sie möchten ein datenbankgestütztes Objekt möglicherweise als "fehlerhaft" markieren können, wenn es nicht gespeicherte Änderungen aufweist. Sie können dies tun, wenn alle DatenaktualisierungenSetFoo()
Methoden durchlaufen .Also: Mit getters / setters können Sie Strukturfelder ändern, während Sie eine kompatible API beibehalten, und Logik um property get / sets hinzufügen, da niemand einfach auf
p.Name = "bob"
Ihren Code verzichten kann.Diese Flexibilität ist relevanter, wenn der Typ kompliziert ist (und die Codebasis groß ist). Wenn Sie eine haben
PersonCollection
, wird diese möglicherweise intern durch einesql.Rows
, eine[]*Person
, eine[]uint
Datenbank-ID oder was auch immer gesichert . Mit der richtigen Schnittstelle können Sie Anrufer davor bewahren, sich darum zu kümmern, wieio.Reader
Netzwerkverbindungen und -dateien aussehen.Eine bestimmte Sache:
interface
s in Go haben die besondere Eigenschaft, dass Sie eine implementieren können, ohne das Paket zu importieren, das sie definiert. Dies kann Ihnen helfen , zyklische Importe zu vermeiden . Wenn Ihre Schnittstelle a zurückgibt*Person
, müssen alle anstelle von Zeichenfolgen oder was auch immerPersonProviders
das Paket importieren, in demPerson
es definiert ist. Das kann in Ordnung oder sogar unvermeidlich sein; Es ist nur eine Konsequenz zu wissen.Aber auch hier hat die Go-Community keine strenge Konvention gegen die Offenlegung von Datenelementen in der öffentlichen API Ihres Typs . Es bleibt Ihrem Urteil überlassen, ob es in einem bestimmten Fall sinnvoll ist, den öffentlichen Zugriff auf ein Attribut als Teil Ihrer API zu verwenden, anstatt eine Gefährdung zu verhindern, da dies möglicherweise eine spätere Implementierungsänderung erschweren oder verhindern könnte.
So lässt die stdlib beispielsweise eine
http.Server
mit Ihrer Konfiguration initialisieren und verspricht, dass eine Nullbytes.Buffer
verwendbar ist. Es ist in Ordnung, solche eigenen Sachen zu machen, und ich denke nicht, dass Sie Dinge präventiv abstrahieren sollten, wenn die konkretere, datenexponierende Version wahrscheinlich funktioniert. Es geht nur darum, sich der Kompromisse bewusst zu werden.quelle
Wenn ich das richtig verstehe, möchten Sie ein Strukturfeld in ein anderes füllen. Meine Meinung, keine Schnittstellen zum Erweitern zu verwenden. Sie können es leicht durch den nächsten Ansatz tun.
https://play.golang.org/p/aBJ5fq3uXtt
Hinweis
Person
in derBob
Erklärung. Dadurch wird das enthaltene Strukturfeld in derBob
Struktur direkt mit etwas syntaktischem Zucker verfügbar .quelle