Menu
Coddy logo textTech

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))  // AAAAA

Ten 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ą.

challenge icon

Wyzwanie

Łatwy

Zbudujmy 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 zwraca io.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) *CountingReader i NewUppercaseWriter() *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 CountingReader wraz z io.ReadAll, aby odczytać wszystkie dane. Przekaż te dane do swojego UppercaseWriter za pomocą jego metody Write.

    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 World

Wynik powinien być następujący:

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

A dla danych:

Go interfaces are powerful

Wynik powinien być następujący:

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

Kluczowe 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)
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Go online