io.Reader i io.Writer
Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 73 z 107.
Biblioteka standardowa Go pokazuje projektowanie oparte na interfejsach za pomocą dwóch fundamentalnych interfejsów: io.Reader i io.Writer. Te proste interfejsy stanowią podstawę wszystkiego, od operacji na plikach po komunikację sieciową.
Interfejs io.Reader wymaga tylko jednej metody:
type Reader interface {
Read(p []byte) (n int, err error)
}Odczytuje do len(p) bajtów do wycinka i zwraca liczbę odczytanych bajtów. Podobnie io.Writer definiuje:
type Writer interface {
Write(p []byte) (n int, err error)
}Każdy typ implementujący te metody współpracuje z całym ekosystemem wejścia/wyjścia.
Oto własny typ implementujący 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
}Teraz ten niestandardowy czytnik współpracuje z każdą funkcją oczekującą io.Reader:
reader := &RepeatReader{Char: 'A', Count: 5}
data, _ := io.ReadAll(reader)
fmt.Println(string(data)) // AAAAATen projekt pozwala pisać funkcje przyjmujące io.Reader lub io.Writer, dzięki czemu działają one z plikami, połączeniami sieciowymi, buforami lub dowolną niestandardową implementacją.
Wyzwanie
ŁatwyZbudujmy niestandardowy procesor strumienia tekstu implementujący interfejsy io.Reader i io.Writer! Utworzysz typy, które mogą przekształcać tekst w miarę jego przepływu, pokazując, jak te podstawowe interfejsy umożliwiają tworzenie zaawansowanych kompozycji operacji wejścia/wyjścia.
Rozmieścisz swój kod w dwóch plikach:
streams.go: Zdefiniuj niestandardowe typy czytnika i zapisującego.Utwórz strukturę
CountingReader, która opakowuje ciąg znaków i śledzi, ile bajtów zostało odczytanych. Powinna mieć pola przechowujące źródłowy ciąg znaków i bieżącą pozycję. Zaimplementuj metodęRead(p []byte) (int, error), która odczytuje bajty ze źródła do przekazanego wycinka, aktualizuje pozycję i zwracaio.EOF, gdy źródło zostanie wyczerpane.Utwórz strukturę
UppercaseWriter, która gromadzi zapisywane dane i zamienia je na wielkie litery. Powinna wewnętrznie przechowywać zgromadzony wynik. Zaimplementuj metodęWrite(p []byte) (int, error), która zamienia otrzymane bajty na wielkie litery i zapisuje je. Dodaj metodęResult() string, aby pobierać zgromadzony tekst zapisany wielkimi literami.Utwórz funkcje konstruktory
NewCountingReader(source string) *CountingReaderiNewUppercaseWriter() *UppercaseWriter, aby prawidłowo inicjalizować swoje typy.main.go: Pokaż działanie niestandardowych typów wejścia/wyjścia z biblioteką standardową.Odczytaj ciąg znaków, a następnie użyj swojego
CountingReaderwraz zio.ReadAll, aby odczytać wszystkie dane. Przekaż te dane do swojegoUppercaseWriterza pomocą jego metodyWrite.Wypisz wyniki w tym formacie:
Original: [input] Uppercase: [result from writer] Bytes read: [total bytes]
Dane wejściowe będą następujące:
- Pojedynczy wiersz tekstu
Na przykład, dla danych:
Hello WorldWynik powinien być następujący:
Original: Hello World
Uppercase: HELLO WORLD
Bytes read: 11A dla danych:
Go interfaces are powerfulWynik powinien być następujący:
Original: Go interfaces are powerful
Uppercase: GO INTERFACES ARE POWERFUL
Bytes read: 26Kluczowe jest to, że Twój CountingReader bezproblemowo współpracuje z io.ReadAll, ponieważ implementuje io.Reader. Każda funkcja w ekosystemie Go, która przyjmuje io.Reader, będzie działać z Twoim niestandardowym typem: na tym polega siła projektowania opartego na interfejsach.
Spróbuj swoich sił
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func main() {
// Odczytaj dane wejściowe
reader := bufio.NewReader(os.Stdin)
input, _ := reader.ReadString('\n')
// Usuń końcowy znak nowej linii, jeśli występuje
if len(input) > 0 && input[len(input)-1] == '\n' {
input = input[:len(input)-1]
}
// TODO: Utwórz CountingReader z ciągiem wejściowym
// countingReader := NewCountingReader(input)
// TODO: Użyj io.ReadAll, aby odczytać wszystkie dane z CountingReader
// data, err := io.ReadAll(countingReader)
// TODO: Utwórz UppercaseWriter
// upperWriter := NewUppercaseWriter()
// TODO: Zapisz dane w UppercaseWriter
// upperWriter.Write(data)
// TODO: Wyświetl wyniki w wymaganym formacie:
// Oryginał: [input]
// Wielkie litery: [wynik z writera]
// Odczytane bajty: [łączna liczba bajtów]
// Element zastępczy zapobiegający błędowi nieużywanego importu — usuń podczas implementacji
_ = io.EOF
fmt.Println("Original:", input)
// fmt.Println("Uppercase:", upperWriter.Result())
// fmt.Println("Bytes read:", countingReader.Position)
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy OOP w Go
Pliki zewnętrzneObszar roboczy i moduły GoPakiety i importyNazwy eksportowane i nieeksportowaneWprowadzenie do OOP w GoStruktury jako klasyDefiniowanie metod dla strukturOdbiorniki wskaźnikowe i wartościoweInicjalizacja strukturFunkcje konstruktoraPodsumowanie – prosty kalkulator4Interfejsy
Wprowadzenie do interfejsówImplementacja niejawnaInterfejs jako kontraktPusty interfejs (any)Asercja typuPrzełącznik typówKompozycja interfejsówInterfejsy Stringer i ErrorPowtórka — kalkulator figur7Hermetyzacja
Pola eksportowane i nieeksportowaneHermetyzacja na poziomie pakietuMetody pobierające i ustawiająceUkrywanie informacji w GoPowtórka – dane studentów10Typy generyczne (Go 1.18+)
Wprowadzenie do typów generycznychParametry typówOgraniczenia typówStruktury generyczneObejście ograniczeń metod generycznychPodsumowanie — generyczna kolekcja2Typy i struktury w praktyce
Typy proste i złożoneDefiniowanie własnych typówTagi pól strukturStruktury anonimoweStruktury zagnieżdżoneWartości zerowe i domyślnePodsumowanie – książka kontaktów5Kompozycja zamiast dziedziczenia
Dlaczego Go nie ma dziedziczeniaPodstawy osadzania strukturPromowanie metodOsadzanie wielu strukturOsadzanie a agregacjaPrzesłanianie osadzonych metodPodsumowanie — hierarchia pracowników8Obsługa błędów i programowanie obiektowe
Interfejs błęduWłasne typy błędówOpakowywanie błędów (fmt.Errorf)Błędy wartowniczeerrors.Is() i errors.As()Panic, Defer i RecoverPodsumowanie — parser plików11Biblioteka standardowa i OOP
io.Reader i io.Writersort.InterfaceInterfejs fmt.Stringerencoding/json ze strukturamiInterfejs http.HandlerPowtórzenie – modele REST API3Wskaźniki i pamięć
Podstawy wskaźników w GoWskaźniki do strukturPrzekazywanie przez wartość a przez referencjęFunkcja new()Odśmiecanie pamięci w GoPowtórka — kreator listy wiązanej6Polimorfizm w Go
Polimorfizm za pomocą interfejsówDuck typing w GoZasady spełniania interfejsówKolekcje polimorficzneWstrzykiwanie zależnościPowtórka — procesor płatności9Współbieżność i OOP
Podstawy goroutinesKanały i komunikacjaKanały buforowane i niebuforowaneInstrukcja selectsync.Mutex i sync.RWMutexsync.WaitGroupProjektowanie struktur bezpiecznych wątkowoPowtórka – pula workerów12Zaawansowane zagadnienia programowania obiektowego
Wzorzec opcji funkcyjnychWzorzec Builder w GoŁańcuchowe wywoływanie metodAliasy typów a definicje typówPodstawy refleksjiPrzegląd generowania koduPoćwicz samodzielnie: Kompilator Go online