Menu
Coddy logo textTech

io.Reader e io.Writer

Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 73 de 107.

La biblioteca estándar de Go muestra el diseño basado en interfaces a través de dos interfaces fundamentales: io.Reader y io.Writer. Estas interfaces sencillas impulsan todo, desde operaciones de archivos hasta comunicación de red.

La interfaz io.Reader requiere solo un método:

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

Lee hasta len(p) bytes en el slice y devuelve cuántos bytes se leyeron. De manera similar, io.Writer define:

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

Cualquier tipo que implemente estos métodos funciona con todo el ecosistema de I/O.

Aquí hay un tipo personalizado que 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
}

Ahora este lector personalizado funciona con cualquier función que espere un io.Reader:

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

Este diseño le permite escribir funciones que aceptan io.Reader o io.Writer, permitiendo que funcionen con archivos, conexiones de red, búferes o cualquier implementación personalizada.

challenge icon

Desafío

Fácil

¡Vamos a construir un procesador de flujo de texto personalizado que implemente las interfaces io.Reader e io.Writer! Crearás tipos que pueden transformar el texto a medida que fluye a través de ellos, demostrando cómo estas interfaces fundamentales permiten una potente composición de E/S.

Organizarás tu código en dos archivos:

  • streams.go: Define tus tipos personalizados de lector y escritor.

    Crea una estructura CountingReader que envuelva una cadena y rastree cuántos bytes se han leído. Debe tener campos para la cadena de origen y un rastreador de posición. Implementa el método Read(p []byte) (int, error) que lee bytes del origen en el slice proporcionado, actualiza la posición y devuelve io.EOF cuando el origen se agota.

    Crea una estructura UppercaseWriter que recolecte los datos escritos y los convierta a mayúsculas. Debe almacenar el resultado acumulado internamente. Implementa el método Write(p []byte) (int, error) que convierte los bytes entrantes a mayúsculas y los almacena. Añade un método Result() string para recuperar el texto en mayúsculas acumulado.

    Crea funciones constructoras NewCountingReader(source string) *CountingReader y NewUppercaseWriter() *UppercaseWriter para inicializar tus tipos correctamente.

  • main.go: Demuestra tus tipos de E/S personalizados trabajando con la biblioteca estándar.

    Lee una cadena de entrada, luego usa tu CountingReader con io.ReadAll para leer todos los datos. Pasa esos datos a través de tu UppercaseWriter usando su método Write.

    Imprime los resultados en este formato:

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

Se proporcionará la siguiente entrada:

  • Una sola línea de texto

Por ejemplo, dado:

Hello World

Tu salida debería ser:

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

Y dado:

Go interfaces are powerful

Tu salida debería ser:

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

La idea clave es que tu CountingReader funciona perfectamente con io.ReadAll porque implementa io.Reader. Cualquier función en el ecosistema de Go que acepte un io.Reader funcionará con tu tipo personalizado; ese es el poder del diseño basado en interfaces.

Hoja de referencia

Las interfaces io.Reader e io.Writer de Go son fundamentales para las operaciones de E/S (I/O):

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

El método Read lee hasta len(p) bytes en el slice y devuelve el número de bytes leídos. El método Write escribe bytes desde el slice y devuelve el número de bytes escritos.

Cualquier tipo que implemente estos métodos funciona con todo el ecosistema de E/S de Go. Ejemplo de un lector personalizado:

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
}

Uso del lector personalizado con funciones de la biblioteca estándar:

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

Este diseño permite que las funciones que aceptan io.Reader o io.Writer trabajen con archivos, conexiones de red, búferes o cualquier implementación personalizada.

Pruébalo tú mismo

package main

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

func main() {
	// Leer entrada
	reader := bufio.NewReader(os.Stdin)
	input, _ := reader.ReadString('\n')
	// Eliminar el salto de línea final si está presente
	if len(input) > 0 && input[len(input)-1] == '\n' {
		input = input[:len(input)-1]
	}

	// TODO: Crear un CountingReader con la cadena de entrada
	// countingReader := NewCountingReader(input)

	// TODO: Usar io.ReadAll para leer todos los datos de tu CountingReader
	// data, err := io.ReadAll(countingReader)

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

	// TODO: Escribir los datos en tu UppercaseWriter
	// upperWriter.Write(data)

	// TODO: Imprimir los resultados en el formato requerido:
	// Original: [input]
	// Uppercase: [result from writer]
	// Bytes read: [total bytes]

	// Marcador de posición para evitar el error de importación no utilizada - eliminar al implementar
	_ = io.EOF
	fmt.Println("Original:", input)
	// fmt.Println("Uppercase:", upperWriter.Result())
	// fmt.Println("Bytes read:", countingReader.Position)
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación Orientada a Objetos