Ein Einstieg in die Programmiersprache Go, Teil 1

Seite 3: Gepufferte Channels

Inhaltsverzeichnis

Die bisher gezeigten Channels hatten keinen Puffer, das heißt Lese- und Schreiboperationen sind genau abzustimmen, damit keine Deadlock-Situation entsteht. Gepufferte Channels können eine bestimmte Anzahl von Werten aufnehmen, bevor Schreiboperationen sie blockieren:

// erzeuge einen channel mit der Puffergröße 1
c := make(chan string, 1)
c <- "text"
fmt.Println(<-c)

funktioniert, da die Schreiboperation nicht blockiert, wohingegen

c := make(chan string, 0) // entspricht "make(chan string)"
c <- "text"
fmt.Println(<-c) // wird nie erreicht

in eine Deadlock-Situation läuft.

Gepufferte Channels sind nützlich, um Optimierungen vorzunehmen oder Produzenten und Konsumenten des Channels zeitlich zu entkoppeln. Eine Go-Routine, die einen gepufferten Channel befüllt, braucht nicht auf das Lesen eines Ergebnisses zu warten, bevor potenziell langwierige Berechnungen für den nächsten Wert starten können. Das ist für verschiedene Problemstellungen nützlich oder sogar notwendig.

Objektorientierung dient in der Softwareentwicklung häufig zur Strukturierung und Modellierung. Obwohl es in Go weder Klassen noch Vererbung gibt, muss man nicht auf typische objektorientierte Konzepte wie Datenkapselung oder Polymorphie verzichten.

Statt Methoden innerhalb von Klassen zu definieren, erfolgt das in Go analog zu Funktionen, aber mit einem zusätzlichen receiver-Parameter. Er folgt nach dem Schlüsselwort func und gibt den Datentyp an, auf dem die Methode definiert wird. Eine Methode muss zum gleichen Package gehören.

// Die Deklaration des Package erfolgt immer am Anfang
package staff

type Clerk struct {
Name string
Age int
salary int
}

// Methodendefinition auf dem Typ *Clerk
func (clerk *Clerk) ChangeSalary(amount int) {
clerk.salary += amount
}

c := Clerk{"Max Mustermann, 30, 40000}

// Mit der Punkt-Notation können dann die Methoden auf den
// jeweiligen Datentypen
// aufgerufen werden, genau wie auf die einzelnen Felder eines
// structs per Punkt-Notation zugegriffen wird.

c.ChangeSalary(100)

Im Beispiel ist die Methode auf einem Pointer vom Typ Clerk definiert anstatt direkt auf dem Struct Clerk. Das ist wichtig, denn die Methode modizifizert das clerk struct. Wäre die Methode direkt auf Clerk definiert, hätte sie eine Call-by-Value-Semantik. Sie operiert dann auf einer Kopie des receiver-Parameters und c.ChangeSalary(100) hätte keinen sichtbaren Effekt, weil nur die Kopie innerhalb der Methode modifiziert ist – c.salary wäre nach dem Methodenaufruf immer noch 40.000.

Methoden können Datentypen und die zugehörigen Operationen bündeln und lassen sich wie Objekte benutzen. Zur Datenkapselung gehört noch das Verstecken der Interna, das heißt im obigen Fall sollen die Felder des Clerk Structs nicht direkt zu modifizieren sein, sondern nur über Methoden. Dazu können Entwickler in Go die Sichtbarkeit von Typen, Funktionen, Methoden, Variablen und Konstanten festlegen.

Die Sichtbarkeit bezieht sich immer auf ein Package und ist durch die Groß-/Kleinschreibung festgelegt. Fangen Typen, Funktionen, Methoden, Variablen oder Konstanten mit einem Kleinbuchstaben an, sind sie nur innerhalb des Packages sichtbar. Fangen sie mit einem Großbuchstaben an, sind sie exportiert und außerhalb des Packages sichtbar. Zu welchem Package Typen, Funktionen, Methoden, Variablen und Konstanten gehören, legt die Package-Deklaration am Anfang der Quelltextdatei fest. Ein Package kann aus mehreren Dateien bestehen.

Bei Structs können Anwender die Sichtbarkeit jedes einzelnen Felds kontrollieren. Im Beispiel sind die Felder Name und Age auch außerhalb des Package staff sichtbar, das Feld salary nicht. Der Zugriff außerhalb von staff kann nur über die exportierte Methode ChangeSalary(amount int) erfolgen.