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)) // AAAAAEste 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.
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
CountingReaderque 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étodoRead(p []byte) (int, error)que lee bytes del origen en el slice proporcionado, actualiza la posición y devuelveio.EOFcuando el origen se agota.Crea una estructura
UppercaseWriterque recolecte los datos escritos y los convierta a mayúsculas. Debe almacenar el resultado acumulado internamente. Implementa el métodoWrite(p []byte) (int, error)que convierte los bytes entrantes a mayúsculas y los almacena. Añade un métodoResult() stringpara recuperar el texto en mayúsculas acumulado.Crea funciones constructoras
NewCountingReader(source string) *CountingReaderyNewUppercaseWriter() *UppercaseWriterpara 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
CountingReaderconio.ReadAllpara leer todos los datos. Pasa esos datos a través de tuUppercaseWriterusando su métodoWrite.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 WorldTu salida debería ser:
Original: Hello World
Uppercase: HELLO WORLD
Bytes read: 11Y dado:
Go interfaces are powerfulTu salida debería ser:
Original: Go interfaces are powerful
Uppercase: GO INTERFACES ARE POWERFUL
Bytes read: 26La 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)) // AAAAAEste 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)
}
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
1Fundamentos de POO en Go
Archivos externosEspacio de trabajo y módulos de GoPaquetes e importacionesNombres exportados vs. no exportadosIntroducción a la POO en GoStructs como clasesDefinición de métodos en structsReceptores de puntero vs. de valorInicialización de structsFunciones constructorasResumen - Calculadora simple4Interfaces
Introducción a las interfacesImplementación implícitaLa interfaz como contratoInterfaz vacía (any)Type AssertionType SwitchComposición de interfacesInterfaces Stringer y ErrorResumen: Calculadora de figuras7Encapsulamiento
Campos exportados vs. no exportadosEncapsulamiento a nivel de paqueteMétodos Getter y SetterOcultamiento de información en GoResumen - Registros de estudiantes10Genéricos (Go 1.18+)
Introducción a los GenéricosParámetros de tipoRestricciones de tipoStructs genéricosSolución alternativa para métodos genéricosResumen - Colección genérica13Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón Abstract FactoryPatrón ObserverPatrón Strategy2Inmersión profunda en Tipos y Structs
Tipos básicos y compuestosDefiniciones de tipos personalizadosEtiquetas de StructsStructs anónimosStructs anidadosValores cero y por defectoRepaso - Libreta de contactos5Composición sobre herencia
Por qué Go no tiene herenciaConceptos básicos de incrustación de structsPromoción de métodosIncrustación de múltiples structsIncrustación vs AgregaciónShadowing de métodos incrustadosResumen - Jerarquía de empleados8Manejo de Errores y OOP
La interfaz errorTipos de errores personalizadosEnvoltura de errores (fmt.Errorf)Errores centinelaerrors.Is() y errors.As()Panic, Defer y RecoverResumen - Analizador de archivos11Biblioteca estándar y POO
io.Reader e io.WriterInterfaz sort.InterfaceInterfaz fmt.Stringerencoding/json con StructsInterfaz http.HandlerRepaso - Modelos de REST API3Punteros y Memoria
Fundamentos de punteros en GoPunteros a StructsPaso por valor vs referenciaLa función new()Garbage Collection en GoResumen - Constructor de Linked List6Polimorfismo en Go
Polimorfismo mediante interfacesDuck Typing en GoReglas de satisfacción de interfacesColecciones polimórficasInyección de dependenciasResumen - Procesador de pagos9Concurrencia y POO
Fundamentos de GoroutinesChannels y comunicaciónChannels con buffer vs. sin bufferSentencia Selectsync.Mutex y sync.RWMutexsync.WaitGroupDiseño de estructuras Thread-SafeResumen - Worker Pool