Menu
Coddy logo textTech

io.Reader & io.Writer

Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 73번째.

Go의 표준 라이브러리는 두 가지 핵심 인터페이스인 io.Readerio.Writer를 통해 인터페이스 기반 설계를 보여줍니다. 이 단순한 인터페이스들은 파일 작업부터 네트워크 통신에 이르기까지 모든 것을 지원합니다.

io.Reader 인터페이스는 단 하나의 메서드만 필요로 합니다:

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

최대 len(p) 바이트를 슬라이스로 읽어 들이고 읽은 바이트 수를 반환합니다. 마찬가지로, io.Writer는 다음과 같이 정의합니다:

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

이 메서드들을 구현하는 모든 타입은 전체 I/O 생태계와 호환됩니다.

다음은 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
}

이제 이 커스텀 리더는 io.Reader를 필요로 하는 모든 함수에서 작동합니다:

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

이러한 설계는 io.Reader 또는 io.Writer를 허용하는 함수를 작성할 수 있게 해주며, 이를 통해 파일, 네트워크 연결, 버퍼 또는 모든 사용자 정의 구현과 함께 작동하도록 만들 수 있습니다.

challenge icon

챌린지

쉬움

io.Readerio.Writer 인터페이스를 구현하는 커스텀 텍스트 스트림 프로세서를 만들어 봅시다! 텍스트가 흐르는 동안 이를 변환할 수 있는 타입을 생성하여, 이러한 기본 인터페이스들이 어떻게 강력한 I/O 구성을 가능하게 하는지 실습해 볼 것입니다.

코드는 두 개의 파일로 구성합니다:

  • streams.go: 커스텀 리더(reader)와 라이터(writer) 타입을 정의합니다.

    문자열을 감싸고 읽은 바이트 수를 추적하는 CountingReader 구조체를 생성하세요. 소스 문자열과 위치 추적기(position tracker)를 위한 필드가 있어야 합니다. 소스에서 제공된 슬라이스로 바이트를 읽고, 위치를 업데이트하며, 소스가 소진되면 io.EOF를 반환하는 Read(p []byte) (int, error) 메서드를 구현하세요.

    작성된 데이터를 수집하여 대문자로 변환하는 UppercaseWriter 구조체를 생성하세요. 내부적으로 누적된 결과를 저장해야 합니다. 들어오는 바이트를 대문자로 변환하여 저장하는 Write(p []byte) (int, error) 메서드를 구현하세요. 누적된 대문자 텍스트를 가져오기 위한 Result() string 메서드를 추가하세요.

    타입을 올바르게 초기화하기 위해 NewCountingReader(source string) *CountingReaderNewUppercaseWriter() *UppercaseWriter 생성자 함수를 만드세요.

  • main.go: 표준 라이브러리와 함께 작동하는 커스텀 I/O 타입을 시연합니다.

    문자열 입력을 읽은 다음, CountingReaderio.ReadAll과 함께 사용하여 모든 데이터를 읽습니다. 해당 데이터를 UppercaseWriterWrite 메서드를 사용하여 전달하세요.

    결과를 다음 형식으로 출력하세요:

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

다음과 같은 입력이 제공됩니다:

  • 텍스트 한 줄

예를 들어, 다음과 같이 주어지면:

Hello World

출력은 다음과 같아야 합니다:

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

그리고 다음과 같이 주어지면:

Go interfaces are powerful

출력은 다음과 같아야 합니다:

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

핵심 통찰은 CountingReaderio.Reader를 구현하기 때문에 io.ReadAll과 원활하게 작동한다는 점입니다. Go 생태계에서 io.Reader를 허용하는 모든 함수는 여러분의 커스텀 타입과 함께 작동할 것입니다. 이것이 바로 인터페이스 기반 설계의 힘입니다.

치트 시트

Go의 io.Readerio.Writer 인터페이스는 I/O 작업의 기본입니다:

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

Read 메서드는 슬라이스에 최대 len(p) 바이트를 읽어 들이고 읽은 바이트 수를 반환합니다. Write 메서드는 슬라이스의 바이트를 쓰고 쓴 바이트 수를 반환합니다.

이 메서드들을 구현하는 모든 타입은 Go의 전체 I/O 에코시스템과 함께 작동합니다. 사용자 정의 리더 예시:

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
}

표준 라이브러리 함수와 함께 사용자 정의 리더 사용하기:

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

이 설계 덕분에 io.Reader 또는 io.Writer를 허용하는 함수가 파일, 네트워크 연결, 버퍼 또는 모든 사용자 정의 구현과 함께 작동할 수 있습니다.

직접 해보기

package main

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

func main() {
	// 입력 읽기
	reader := bufio.NewReader(os.Stdin)
	input, _ := reader.ReadString('\n')
	// 줄바꿈 문자가 있으면 제거
	if len(input) > 0 && input[len(input)-1] == '\n' {
		input = input[:len(input)-1]
	}

	// TODO: 입력 문자열로 CountingReader 생성
	// countingReader := NewCountingReader(input)

	// TODO: io.ReadAll을 사용하여 CountingReader의 모든 데이터 읽기
	// data, err := io.ReadAll(countingReader)

	// TODO: UppercaseWriter 생성
	// upperWriter := NewUppercaseWriter()

	// TODO: UppercaseWriter에 데이터 쓰기
	// upperWriter.Write(data)

	// TODO: 요구된 형식으로 결과 출력:
	// Original: [input]
	// Uppercase: [result from writer]
	// Bytes read: [total bytes]

	// 사용하지 않는 import 에러를 방지하기 위한 플레이스홀더 - 구현 시 제거
	_ = io.EOF
	fmt.Println("Original:", input)
	// fmt.Println("Uppercase:", upperWriter.Result())
	// fmt.Println("Bytes read:", countingReader.Position)
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨