Menu
Coddy logo textTech

io.Reader e io.Writer

Fa parte della sezione Programmazione orientata agli oggetti del percorso GO di Coddy. Lezione 73 di 107.

La libreria standard di Go mostra la progettazione basata sulle interfacce attraverso due interfacce fondamentali: io.Reader e io.Writer. Queste semplici interfacce sono alla base di tutto, dalle operazioni sui file alla comunicazione di rete.

L'interfaccia io.Reader richiede un solo metodo:

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

Legge fino a len(p) byte nello slice e restituisce quanti byte sono stati letti. Analogamente, io.Writer definisce:

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

Qualsiasi tipo che implementa questi metodi funziona con l'intero ecosistema di I/O.

Ecco un tipo personalizzato che implementa io.Reader:

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
}

Ora questo lettore personalizzato funziona con qualsiasi funzione che accetta un io.Reader:

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

Questo design ti permette di scrivere funzioni che accettano io.Reader o io.Writer, consentendo loro di funzionare con file, connessioni di rete, buffer o qualsiasi implementazione personalizzata.

challenge icon

Sfida

Facile

Costruiamo un processore personalizzato di flussi di testo che implementa le interfacce io.Reader e io.Writer! Creerai dei tipi in grado di trasformare il testo man mano che scorre al loro interno, mostrando come queste interfacce fondamentali consentano una potente composizione dell'I/O.

Organizzerai il codice in due file:

  • streams.go: definisci i tuoi tipi personalizzati di lettore e scrittore.

    Crea una struct CountingReader che avvolga una stringa e tenga traccia di quanti byte sono stati letti. Dovrebbe avere campi per la stringa sorgente e per tenere traccia della posizione. Implementa il metodo Read(p []byte) (int, error) che legge i byte dalla sorgente nella slice fornita, aggiorna la posizione e restituisce io.EOF quando la sorgente è esaurita.

    Crea una struct UppercaseWriter che raccoglie i dati scritti e li converte in maiuscolo. Dovrebbe memorizzare internamente il risultato accumulato. Implementa il metodo Write(p []byte) (int, error) che converte in maiuscolo i byte ricevuti e li memorizza. Aggiungi un metodo Result() string per recuperare il testo maiuscolo accumulato.

    Crea le funzioni costruttrici NewCountingReader(source string) *CountingReader e NewUppercaseWriter() *UppercaseWriter per inizializzare correttamente i tuoi tipi.

  • main.go: mostra i tuoi tipi I/O personalizzati al lavoro con la libreria standard.

    Leggi una stringa in input, poi usa il tuo CountingReader con io.ReadAll per leggere tutti i dati. Passa quei dati attraverso il tuo UppercaseWriter usando il suo metodo Write.

    Stampa i risultati in questo formato:

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

Verrà fornito il seguente input:

  • Una singola riga di testo

Per esempio, dato:

Hello World

Il tuo output dovrebbe essere:

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

E dato:

Go interfaces are powerful

Il tuo output dovrebbe essere:

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

L'idea chiave è che il tuo CountingReader funziona senza problemi con io.ReadAll perché implementa io.Reader. Qualsiasi funzione nell'ecosistema Go che accetta un io.Reader funzionerà con il tuo tipo personalizzato: questa è la potenza della progettazione basata sulle interfacce.

Provalo tu

package main

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

func main() {
	// Leggi l'input
	reader := bufio.NewReader(os.Stdin)
	input, _ := reader.ReadString('\n')
	// Rimuovi il carattere di nuova riga finale, se presente
	if len(input) > 0 && input[len(input)-1] == '\n' {
		input = input[:len(input)-1]
	}

	// TODO: Crea un CountingReader con la stringa di input
	// countingReader := NewCountingReader(input)

	// TODO: Usa io.ReadAll per leggere tutti i dati dal tuo CountingReader
	// data, err := io.ReadAll(countingReader)

	// TODO: Crea un UppercaseWriter
	// upperWriter := NewUppercaseWriter()

	// TODO: Scrivi i dati nel tuo UppercaseWriter
	// upperWriter.Write(data)

	// TODO: Stampa i risultati nel formato richiesto:
	// Originale: [input]
	// Maiuscolo: [risultato del writer]
	// Byte letti: [byte totali]

	// Segnaposto per evitare un errore dovuto a un import inutilizzato - rimuovere durante l'implementazione
	_ = io.EOF
	fmt.Println("Original:", input)
	// fmt.Println("Uppercase:", upperWriter.Result())
	// fmt.Println("Bytes read:", countingReader.Position)
}
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Compilatore Go online