Ein Struct ist ein benutzerdefinierter Datentyp, der eine Sammlung von Feldern gruppiert. Structs sind ähnlich wie Klassen in anderen Programmiersprachen, bieten jedoch keine Vererbung.
Ein Struct wird mit dem Schlüsselwort type deklariert,
gefolgt vom Namen des Structs und dem Schlüsselwort struct.
Hier ein Beispiel:
type Person struct {
Name string
Age int
}In diesem Beispiel haben wir ein Struct namens Person
deklariert, das zwei Felder enthält: Name vom Typ
string und Age vom Typ int.
Um eine Instanz eines Structs zu erstellen, verwenden wir die Struct-Literal-Syntax:
person := Person{Name: "Alice", Age: 30}
fmt.Println("Name:", person.Name)
fmt.Println("Age:", person.Age)Hier erstellen wir eine Instanz von Person mit dem Namen
“Alice” und dem Alter 30. Wir greifen auf die Felder Name
und Age zu, indem wir die Punktnotation verwenden.
Wie in der vorherigen Folge besprochen, können wir Methoden für
Structs definieren. Lassen Sie uns eine Methode für unser
Person-Struct hinzufügen:
func (p Person) Greet() string {
return "Hello, my name is " + p.Name
}In diesem Beispiel haben wir eine Methode Greet
definiert, die eine Begrüßung basierend auf dem Namen der Person
zurückgibt. Um die Methode aufzurufen, verwenden wir wieder die
Punktnotation:
fmt.Println(person.Greet())Ein Interface in Go ist eine Sammlung von Methoden-Signaturen. Ein Typ implementiert ein Interface, indem er alle Methoden dieses Interfaces definiert. Interfaces sind ein mächtiges Werkzeug, um Polymorphismus in Go zu erreichen.
Ein Interface wird mit dem Schlüsselwort type
deklariert, gefolgt vom Namen des Interfaces und dem Schlüsselwort
interface. Hier ein Beispiel:
type Greeter interface {
Greet() string
}In diesem Beispiel haben wir ein Interface namens
Greeter deklariert, das eine Methode Greet mit
der Rückgabe eines string-Wertes enthält.
Unser Person-Struct implementiert bereits das
Greeter-Interface, da es die Methode Greet
definiert. Wir können dies verifizieren, indem wir eine Funktion
schreiben, die einen Greeter akzeptiert:
func SayHello(g Greeter) {
fmt.Println(g.Greet())
}Nun können wir eine Instanz von Person an diese Funktion
übergeben:
person := Person{Name: "Alice", Age: 30}
SayHello(person)Da Person die Methode Greet implementiert,
ist person ein gültiges Argument für die Funktion
SayHello.
Lassen Sie uns ein praktisches Beispiel betrachten, um die
Nützlichkeit von Interfaces zu demonstrieren. Angenommen, wir haben ein
weiteres Struct Robot:
type Robot struct {
ID int
}
func (r Robot) Greet() string {
return "Hello, I am robot number " + strconv.Itoa(r.ID)
}Hier haben wir ein Struct Robot mit einer Methode
Greet, die die ID des Roboters zurückgibt. Da
Robot ebenfalls das Greeter-Interface
implementiert, können wir es in der SayHello-Funktion
verwenden:
robot := Robot{ID: 42}
SayHello(robot)In diesem Beispiel übergeben wir eine Instanz von Robot
an die SayHello-Funktion, die die Begrüßung des Roboters
ausgibt.
Betrachten wir nun ein weiteres Beispiel mit einem Interface.
Angenommen, wir haben ein Interface Shaper, das eine
Methode Area zur Berechnung der Fläche enthält:
type Shaper interface {
Area() float64
}Nun definieren wir zwei Structs, Rectangle und
Circle, die das Shaper-Interface
implementieren:
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}Hier haben wir die Structs Rectangle und
Circle mit der Methode Area versehen, die die
Fläche des jeweiligen Shapes berechnet. Da beide Structs das
Shaper-Interface implementieren, können wir sie in einer
Funktion verwenden, die ein Shaper akzeptiert:
func PrintArea(s Shaper) {
fmt.Printf("The area is %f\n", s.Area())
}Wir können nun Instanzen von Rectangle und
Circle an diese Funktion übergeben:
rect := Rectangle{Width: 10, Height: 5}
circle := Circle{Radius: 7}
PrintArea(rect)
PrintArea(circle)In diesem Beispiel übergeben wir Instanzen von Rectangle
und Circle an die PrintArea-Funktion, die die
Fläche der jeweiligen Shapes ausgibt.
Das Builder Pattern wird verwendet, um komplexe Objekte Schritt für Schritt zu erstellen. In Go kann es wie folgt implementiert werden:
package main
import "fmt"
// Product ist das komplexe Objekt, das erstellt werden soll.
type Product struct {
part1 string
part2 string
part3 string
}
// Builder ist das Interface, das die Erstellung der einzelnen Teile des Produkts definiert.
type Builder interface {
SetPart1(part1 string) Builder
SetPart2(part2 string) Builder
SetPart3(part3 string) Builder
Build() Product
}
// ConcreteBuilder ist die konkrete Implementierung des Builders.
type ConcreteBuilder struct {
product Product
}
func (b *ConcreteBuilder) SetPart1(part1 string) Builder {
b.product.part1 = part1
return b
}
func (b *ConcreteBuilder) SetPart2(part2 string) Builder {
b.product.part2 = part2
return b
}
func (b *ConcreteBuilder) SetPart3(part3 string) Builder {
b.product.part3 = part3
return b
}
func (b *ConcreteBuilder) Build() Product {
return b.product
}
// Director definiert die Schritte zur Erstellung des Produkts.
type Director struct {
builder Builder
}
func NewDirector(builder Builder) *Director {
return &Director{builder: builder}
}
func (d *Director) Construct() Product {
return d.builder.SetPart1("Part 1").
SetPart2("Part 2").
SetPart3("Part 3").
Build()
}
func main() {
builder := &ConcreteBuilder{}
director := NewDirector(builder)
product := director.Construct()
fmt.Printf("Product: %+v\n", product)
}Das Builder Pattern ermöglicht die flexible und kontrollierte Erstellung komplexer Objekte, indem es die Konstruktion von der Repräsentation trennt.