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)) // AAAAAQuesto 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.
Sfida
FacileCostruiamo 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
CountingReaderche 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 metodoRead(p []byte) (int, error)che legge i byte dalla sorgente nella slice fornita, aggiorna la posizione e restituisceio.EOFquando la sorgente è esaurita.Crea una struct
UppercaseWriterche raccoglie i dati scritti e li converte in maiuscolo. Dovrebbe memorizzare internamente il risultato accumulato. Implementa il metodoWrite(p []byte) (int, error)che converte in maiuscolo i byte ricevuti e li memorizza. Aggiungi un metodoResult() stringper recuperare il testo maiuscolo accumulato.Crea le funzioni costruttrici
NewCountingReader(source string) *CountingReadereNewUppercaseWriter() *UppercaseWriterper 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
CountingReaderconio.ReadAllper leggere tutti i dati. Passa quei dati attraverso il tuoUppercaseWriterusando il suo metodoWrite.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 WorldIl tuo output dovrebbe essere:
Original: Hello World
Uppercase: HELLO WORLD
Bytes read: 11E dato:
Go interfaces are powerfulIl tuo output dovrebbe essere:
Original: Go interfaces are powerful
Uppercase: GO INTERFACES ARE POWERFUL
Bytes read: 26L'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)
}
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
1Fondamenti di OOP in Go
File esterniWorkspace e moduli GoPacchetti e importazioniNomi esportati e non esportatiIntroduzione all’OOP in GoGli struct come classiDefinire metodi sugli structReceiver con puntatore vs receiver con valoreInizializzazione degli structFunzioni costruttriciRiepilogo - Calcolatrice semplice4Interfacce
Introduzione alle interfacceImplementazione implicitaL’interfaccia come contrattoInterfaccia vuota (any)Asserzione di tipoSwitch sul tipoComposizione di interfacceInterfacce Stringer ed ErrorRipasso - Calcolatore di forme7Incapsulamento
Campi esportati e non esportatiIncapsulamento a livello di packageMetodi Getter e SetterNascondimento delle informazioni in GoRiepilogo - Registri degli studenti10Tipi generici (Go 1.18+)
Introduzione ai tipi genericiParametri di tipoVincoli di tipoStruct genericheSoluzione alternativa per i metodi genericiRiepilogo - Collezione generica13Modelli di progettazione Parte 1
Introduzione ai modelli di progettazioneModello SingletonModello FactoryModello Abstract FactoryModello ObserverModello Strategy2Approfondimento su tipi e struct
Tipi di base e compostiDefinizioni di tipi personalizzatiTag delle structStruct anonimeStruct annidateValori zero e predefinitiRipasso - Rubrica5Composizione invece dell'ereditarietà
Perché Go non ha l'ereditarietàFondamenti dell'embedding delle structPromozione dei metodiEmbedding di più structEmbedding e aggregazione a confrontoMascheramento dei metodi embeddedRiepilogo: gerarchia dei dipendenti8Gestione degli errori e OOP
L'interfaccia errorTipi di errore personalizzatiWrapping degli errori (fmt.Errorf)Errori sentinellaerrors.Is() ed errors.As()Panic, Defer e RecoverRiepilogo - Parser di file11Libreria standard e OOP
io.Reader e io.Writersort.InterfaceInterfaccia fmt.Stringerencoding/json con le structInterfaccia http.HandlerRipasso - modelli REST API14Modelli di progettazione – Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeMiddleware come Decorator3Puntatori e memoria
Concetti base dei puntatori in GoPuntatori a structPassaggio per valore vs per riferimentoLa funzione new()Garbage collection in GoRiepilogo - Costruttore di liste concatenate6Polimorfismo in Go
Polimorfismo tramite interfacceDuck typing in GoRegole di soddisfacimento delle interfacceCollezioni polimorficheIniezione delle dipendenzeRiepilogo - Elaboratore di pagamenti9Concorrenza e OOP
Basi delle goroutineCanali e comunicazioneCanali bufferizzati vs non bufferizzatiIstruzione selectsync.Mutex e sync.RWMutexsync.WaitGroupProgettazione di struct thread-safeRiepilogo: worker poolEsercitati da solo: Compilatore Go online