Menu
Coddy logo textTech

io.Reader & io.Writer

Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 73 von 107.

Die Standardbibliothek von Go zeigt schnittstellenbasiertes Design anhand zweier grundlegender Schnittstellen: io.Reader und io.Writer. Diese einfachen Schnittstellen ermöglichen alles – von Dateioperationen bis hin zur Netzwerkkommunikation.

Das io.Reader-Interface erfordert nur eine Methode:

type Reader interface {
    Read(p []byte) (n int, err error)
}

Es liest bis zu len(p) bytes in das Slice ein und gibt zurück, wie viele bytes gelesen wurden. Ebenso definiert io.Writer:

type Writer interface {
    Write(p []byte) (n int, err error)
}

Jeder Typ, der diese Methoden implementiert, funktioniert mit dem gesamten I/O-Ökosystem.

Hier ist ein benutzerdefinierter Typ, der io.Reader implementiert:

type RepeatReader struct {
    Char  byte
    Count int
    pos   int
}

func (r *RepeatReader) Read(p []byte) (int, error) {
    if r.pos >= r.Count {
        return 0, io.EOF
    }
    n := 0
    for n < len(p) && r.pos < r.Count {
        p[n] = r.Char
        n++
        r.pos++
    }
    return n, nil
}

Jetzt funktioniert dieser benutzerdefinierte Reader mit jeder Funktion, die einen io.Reader erwartet:

reader := &RepeatReader{Char: 'A', Count: 5}
data, _ := io.ReadAll(reader)
fmt.Println(string(data))  // AAAAA

Mit diesem Entwurf kannst du Funktionen schreiben, die io.Reader oder io.Writer akzeptieren, sodass sie mit Dateien, Netzwerkverbindungen, Puffern oder jeder benutzerdefinierten Implementierung funktionieren.

challenge icon

Aufgabe

Einfach

Lass uns einen benutzerdefinierten Textstream-Prozessor erstellen, der die Schnittstellen io.Reader und io.Writer implementiert! Du erstellst Typen, die Text während seines Durchlaufs transformieren können, und demonstrierst damit, wie diese grundlegenden Schnittstellen eine leistungsfähige I/O-Komposition ermöglichen.

Du organisierst deinen Code über zwei Dateien:

  • streams.go: Definiere deine benutzerdefinierten Reader- und Writer-Typen.

    Erstelle eine CountingReader-Struktur, die einen String umschließt und verfolgt, wie viele Bytes gelesen wurden. Sie sollte Felder für den Quell-String und einen Positionszähler besitzen. Implementiere die Methode Read(p []byte) (int, error), die Bytes aus der Quelle in das bereitgestellte Slice liest, die Position aktualisiert und io.EOF zurückgibt, wenn die Quelle erschöpft ist.

    Erstelle eine UppercaseWriter-Struktur, die geschriebene Daten sammelt und in Großbuchstaben umwandelt. Sie sollte das angesammelte Ergebnis intern speichern. Implementiere die Methode Write(p []byte) (int, error), die eingehende Bytes in Großbuchstaben umwandelt und speichert. Füge eine Methode Result() string hinzu, um den angesammelten Text in Großbuchstaben abzurufen.

    Erstelle Konstruktorfunktionen NewCountingReader(source string) *CountingReader und NewUppercaseWriter() *UppercaseWriter, um deine Typen ordnungsgemäß zu initialisieren.

  • main.go: Demonstriere, wie deine benutzerdefinierten I/O-Typen mit der Standardbibliothek funktionieren.

    Lies eine String-Eingabe ein und verwende dann deinen CountingReader mit io.ReadAll, um alle Daten zu lesen. Übertrage diese Daten mithilfe der UppercaseWriter-Methode an deinen Write.

    Gib die Ergebnisse in diesem Format aus:

    Original: [input]
    Uppercase: [result from writer]
    Bytes read: [total bytes]

Die folgende Eingabe wird bereitgestellt:

  • Eine einzelne Textzeile

Zum Beispiel bei folgender Eingabe:

Hello World

Deine Ausgabe sollte wie folgt aussehen:

Original: Hello World
Uppercase: HELLO WORLD
Bytes read: 11

Und bei folgender Eingabe:

Go interfaces are powerful

sollte deine Ausgabe wie folgt aussehen:

Original: Go interfaces are powerful
Uppercase: GO INTERFACES ARE POWERFUL
Bytes read: 26

Die zentrale Erkenntnis ist, dass dein CountingReader nahtlos mit io.ReadAll funktioniert, weil er io.Reader implementiert. Jede Funktion im Go-Ökosystem, die ein io.Reader akzeptiert, funktioniert mit deinem benutzerdefinierten Typ: Das ist die Stärke eines schnittstellenbasierten Designs.

Probier es selbst

package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

func main() {
	// Eingabe lesen
	reader := bufio.NewReader(os.Stdin)
	input, _ := reader.ReadString('\n')
	// Nachgestellten Zeilenumbruch entfernen, falls vorhanden
	if len(input) > 0 && input[len(input)-1] == '\n' {
		input = input[:len(input)-1]
	}

	// TODO: Erstelle einen CountingReader mit dem Eingabestring
	// countingReader := NewCountingReader(input)

	// TODO: Verwende io.ReadAll, um alle Daten von deinem CountingReader zu lesen
	// data, err := io.ReadAll(countingReader)

	// TODO: Erstelle einen UppercaseWriter
	// upperWriter := NewUppercaseWriter()

	// TODO: Schreibe die Daten in deinen UppercaseWriter
	// upperWriter.Write(data)

	// TODO: Gib die Ergebnisse im erforderlichen Format aus:
	// Original: [input]
	// Uppercase: [Ergebnis vom Writer]
	// Bytes read: [Gesamtbytes]

	// Platzhalter, um einen Fehler wegen ungenutztem Import zu vermeiden – beim Implementieren entfernen
	_ = io.EOF
	fmt.Println("Original:", input)
	// fmt.Println("Uppercase:", upperWriter.Result())
	// fmt.Println("Bytes read:", countingReader.Position)
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Online-Go-Compiler