Grundlagen der Reflection
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 83 von 107.
Reflektion ermöglicht es einem Programm, seine eigene Struktur zur Laufzeit zu untersuchen und zu bearbeiten. Gos Paket reflect stellt diese Funktionalität bereit und ermöglicht es dir, Typen zu untersuchen, Struct-Felder zu lesen und Methoden dynamisch aufzurufen.
Die beiden grundlegenden Typen bei der Reflektion sind reflect.Type und reflect.Value. Du erhältst sie mit reflect.TypeOf() und reflect.ValueOf():
import "reflect"
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 30}
t := reflect.TypeOf(p)
v := reflect.ValueOf(p)
fmt.Println(t.Name()) // Person
fmt.Println(t.Kind()) // struct
fmt.Println(v.NumField()) // 2Du kannst über Struct-Felder iterieren, um ihre Namen, Typen und Werte zu untersuchen:
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
fmt.Printf("%s: %v\n", field.Name, value)
}
// Ausgabe:
// Name: Alice
// Age: 30Reflection ist leistungsstark, bringt aber Kompromisse mit sich: Sie umgeht die Typprüfung zur Kompilierzeit, läuft langsamer als der direkte Zugriff und macht den Code schwerer verständlich. Verwende sie sparsam für Aufgaben wie JSON-Marshaling, ORM-Mapping oder das Erstellen generischer Dienstprogramme, bei denen der Typ zur Kompilierzeit nicht bekannt ist.
Aufgabe
EinfachErstelle einen Struct-Inspektor, der Reflection verwendet, um jeden Struct zu untersuchen und seine Struktur zu melden! Dies ist ein praktischer Anwendungsfall für Reflection: das Erstellen von Hilfsprogrammen, die mit Typen arbeiten, die zur Kompilierzeit unbekannt sind.
Du organisierst deinen Code auf zwei Dateien verteilt:
inspector.go: Erstelle dein auf Reflection basierendes Inspektionsprogramm.Erstelle eine Funktion
InspectStruct(v any) string, die das Paketreflectverwendet, um jeden an sie übergebenen Struct zu untersuchen. Deine Funktion soll einen formatierten Bericht zurückgeben, der Folgendes enthält:- Den Typnamen des Structs
- Die Art des Structs (sollte "struct" sein)
- Die Anzahl der Felder
- Für jedes Feld: den Feldnamen, seinen Typ und seinen aktuellen Wert
Das Ausgabeformat sollte wie folgt aussehen:
Type: [TypeName] Kind: struct Fields: [count] - [FieldName] ([FieldType]): [Value] - [FieldName] ([FieldType]): [Value] ...Verwende
reflect.TypeOf(), um Typinformationen abzurufen, undreflect.ValueOf(), um auf Feldwerte zuzugreifen. Iteriere mithilfe vonNumField()über die Felder, mitField(i)auf dem Typ für Metadaten und mitField(i)auf dem Wert für die tatsächlichen Werte.main.go: Definiere Beispiel-Structs und verwende deinen Inspektor.Lies einen Struct-Typ (
productoderemployee) und seine Feldwerte ein, erstelle anschließend den entsprechenden Struct und untersuche ihn.Für
product: Erstelle einenProduct-Struct mit den FeldernName(string),Price(float64) undInStock(bool). Lies den Namen, den Preis und den Lagerstatus ein.Für
employee: Erstelle einenEmployee-Struct mit den FeldernID(int),Name(string) undDepartment(string). Lies die ID, den Namen und die Abteilung ein.Übergib den erstellten Struct an
InspectStructund gib das Ergebnis aus.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Struct-Typ (
productoderemployee) - Folgende Zeilen: Feldwerte für diesen Struct-Typ
Beispiel für folgende Eingabe:
product
Laptop
999.99
trueDeine Ausgabe sollte wie folgt aussehen:
Type: Product
Kind: struct
Fields: 3
- Name (string): Laptop
- Price (float64): 999.99
- InStock (bool): trueUnd bei folgender Eingabe:
employee
42
Alice Johnson
EngineeringDeine Ausgabe sollte wie folgt aussehen:
Type: Employee
Kind: struct
Fields: 3
- ID (int): 42
- Name (string): Alice Johnson
- Department (string): EngineeringUnd bei folgender Eingabe:
product
Headphones
79.50
falseDeine Ausgabe sollte wie folgt aussehen:
Type: Product
Kind: struct
Fields: 3
- Name (string): Headphones
- Price (float64): 79.5
- InStock (bool): falseBeachte, wie dein Inspektor mit unterschiedlichen Struct-Typen arbeitet, ohne ihre Struktur im Voraus zu kennen. Das ist die Stärke von Reflection! Dieselbe Funktion InspectStruct verarbeitet sowohl Product- als auch Employee-Structs dynamisch.
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
// Product-Struct mit den Feldern Name, Price und InStock
type Product struct {
Name string
Price float64
InStock bool
}
// Employee-Struct mit den Feldern ID, Name und Department
type Employee struct {
ID int
Name string
Department string
}
func main() {
reader := bufio.NewReader(os.Stdin)
// Struct-Typ einlesen
structType, _ := reader.ReadString('\n')
structType = strings.TrimSpace(structType)
if structType == "product" {
// Product-Felder einlesen
name, _ := reader.ReadString('\n')
name = strings.TrimSpace(name)
priceStr, _ := reader.ReadString('\n')
priceStr = strings.TrimSpace(priceStr)
price, _ := strconv.ParseFloat(priceStr, 64)
inStockStr, _ := reader.ReadString('\n')
inStockStr = strings.TrimSpace(inStockStr)
inStock, _ := strconv.ParseBool(inStockStr)
// TODO: Erstelle ein Product-Struct mit den gelesenen Werten
// TODO: Rufe InspectStruct mit dem Produkt auf und gib das Ergebnis aus
} else if structType == "employee" {
// Employee-Felder einlesen
idStr, _ := reader.ReadString('\n')
idStr = strings.TrimSpace(idStr)
id, _ := strconv.Atoi(idStr)
name, _ := reader.ReadString('\n')
name = strings.TrimSpace(name)
department, _ := reader.ReadString('\n')
department = strings.TrimSpace(department)
// TODO: Erstelle ein Employee-Struct mit den gelesenen Werten
// TODO: Rufe InspectStruct mit dem Employee auf und gib das Ergebnis aus
}
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP in Go
Externe DateienGo-Workspace & ModulePackages & ImportsExportierte vs. nicht exportierte NamenEinführung in OOP mit GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktorfunktionenRückblick – Einfacher Taschenrechner4Schnittstellen
Einführung in SchnittstellenImplizite ImplementierungSchnittstelle als VertragLeere Schnittstelle (any)TypzusicherungTypwechselZusammensetzung von SchnittstellenStringer- und Error-SchnittstellenRückblick – Formenrechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf PaketebeneGetter- und Setter-MethodenInformationsverbergung in GoRückblick – Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterTypbeschränkungenGenerische StrukturenWorkaround für generische MethodenZusammenfassung – Generische Sammlung2Typen & Structs im Detail
Grundlegende & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct-TagsAnonyme StructsVerschachtelte StructsNullwerte & StandardwerteRückblick – Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen der Struct-EinbettungMethoden-PromotionMehrere Structs einbettenEinbettung vs. AggregationVerbergen eingebetteter MethodenRückblick – Mitarbeiterhierarchie8Fehlerbehandlung & OOP
Das Error-InterfaceBenutzerdefinierte FehlertypenError-Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverRückblick – Dateiparser3Zeiger & Speicher
Grundlagen von Zeigern in GoZeiger auf StructsÜbergabe per Wert vs. ReferenzDie Funktion new()Garbage Collection in GoRückblick – Verkettete Liste erstellen6Polymorphismus in Go
Polymorphismus über InterfacesDuck-Typing in GoRegeln zur Interface-ErfüllungPolymorphe SammlungenDependency InjectionZusammenfassung – Zahlungsprozessor9Konkurrenz & OOP
Grundlagen der GoroutinesChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignRückblick – Worker Pool12Fortgeschrittene OOP-Konzepte
Functional-Options-PatternBuilder-Pattern in GoMethod ChainingTyp-Aliase vs. DefinitionenGrundlagen der ReflectionÜberblick über CodegenerierungÜbe selbstständig: Online-Go-Compiler