Interfaces in Go: reine Typ-Sache

Seite 3: Interfaces zur Laufzeit

Inhaltsverzeichnis

Go bietet Werkzeuge, mit denen sich Interfaces während der Laufzeit näher untersuchen lassen. Sie sind hilfreich, um Interface-Variablen im Code näher zu betrachten. Dabei gilt es jedoch zu beachten, dass das Zuweisen des darunterliegenden Werts erst zur Laufzeit erfolgt.

Das erste Werkzeug dient der Prüfung auf nil. Dieses bereits in Listing 6 vorgestellte Pattern dient in der Regel der Fehlerbehandlung.

Listing 8: Prüfen auf nil

err := openSomething(name)
if err != nil {
        // Fehlerbehandlung hier
}

Die Prüfung auf nil zeigt nur, ob ein Wert existiert, aber nicht, welcher sich konkret dahinter verbirgt. Hierfür gibt es den Type-Switch und die Type-Assertion. Diese Werkzeuge holen, wie in Abbildung 5 gezeigt, die Variable aus der Hülle heraus.

Wert aus dem Interface (Abb. 5).

Der Type-Switch erlaubt es nun die Interface-Variablen auf mehrere verschiedene Typen zu prüfen. Die jeweilige Prüfung erfolgt innerhalb der Case-Anweisung. Innerhalb dieses Blocks ist dann der Typ der Variable bekannt und der darunterliegende Typ lässt sich somit direkt verwenden.

Listing 9: Type-Switch

func myFunc(s Stringer) {
        switch v := s.(type) {
        case nil:
                fmt.Println("nil Pointer")
        case *bytes.Buffer:
                fmt.Println("bytes.Buffer", v.Len())
        default:
                fmt.Println("Typ unbekannt")
    }
}

Listing 9 zeigt die Syntax des Type-Switch. Auch hier ist eine Prüfung auf nil möglich. Im Fall des *bytes.Buffer ist es über die Variable v möglich, auf alle Felder, Eigenschaften oder Methoden dieses Typs zuzugreifen. Im vorliegenden Beispiel wäre das die Methode Len().

Der Type-Switch bietet sich immer dann an, wenn mehrere unterschiedliche Typen zu prüfen sind. Wollen Entwicklerinnen und Entwickler lediglich auf einen bestimmten Typ prüfen, sollten sie vorzugsweise die Type-Assertion verwenden. Dabei wird versucht einer Variablen einen bestimmten Typ zuzuweisen.

Die zugehörige Syntax ist einfach gehalten. Nach der Interface-Variablen kommt der Typ in runde Klammern. Die Type-Assertion hat immer zwei Rückgabeparameter, wobei der zweite Parameter optional ist. Sollte dieser nicht abgefragt werden, kann es zu einer Panik kommen. Das passiert, sobald der Typ aus der Interface-Hülle nicht dem erwarteten Typ entspricht.

Listing 10: Type Assertion

func myFunc(r io.Reader) {
    buf, ok := r.(*bytes.Buffer)
    if ok {
        fmt.Println(buf.Bytes())
    }
}

Listing 10 zeigt das Standard-Pattern zur Type-Assertion. Die Variable ok prüft, ob die Umwandlung erfolgreich ist. Die Variable buf ist vom Typ *bytes.Buffer und lässt sich als solche verwenden. Sollte die Type-Assertion nicht erfolgreich gewesen sein, so besitzt buf den Nullwert des jeweiligen Typs. Da es sich hier um einen Pointer handelt, wäre dies nil.

Type-Switch und Type-Assertion funktionieren auch bei Interface-Typen. Das bedeutet: ein Interface lässt sich in ein anderes Interface umwandeln.

Listing 11: Interface-Umwandlung

func ReadAndClose(r io.Reader) ([]byte, error) {
        type closer interface {
                Close()
        }
        c, ok := r.(closer)
        if ok {
                defer c.Close()
        }
        return ioutil.ReadAll(r)
}

Listing 11 beschreibt beispielhaft die Umwandlung eines Interface in ein anderes. Die Type-Assertion ist an dieser Stelle sehr hilfreich. Denn die Methode Close() ist für die Funktion nicht grundsätzlich nötig. Die Funktion kann aber dennoch die Methode verwenden. Durch if ok ist sichergestellt, dass die Methode auch nur dann verwendet wird, wenn sie vorhanden ist.