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) bytes를 읽고, 읽은 bytes의 개수를 반환합니다. 마찬가지로 io.Writer는 다음을 정의합니다:

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

이러한 메서드를 구현하는 모든 타입은 전체 I/O 생태계와 함께 작동합니다.

다음은 io.Reader를 implements하는 사용자 지정 타입입니다:

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는 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 INTERFACES를 implements하는 사용자 지정 텍스트 스트림 프로세서를 만들어 봅시다! 텍스트가 이를 통과하며 흐를 때 텍스트를 변환할 수 있는 타입을 만들고, 이러한 기본 INTERFACES가 어떻게 강력한 I/O 조합을 가능하게 하는지 살펴봅니다.

코드를 두 파일로 구성합니다:

  • streams.go: 사용자 지정 reader 및 writer 타입을 정의합니다.

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

    기록된 data를 collect하고 이를 대문자로 converts하는 UppercaseWriter 구조체를 만듭니다. accumulated result를 내부에 저장해야 합니다. 들어오는 바이트를 대문자로 converts하여 저장하는 Write(p []byte) (int, error) 메서드를 implements합니다. accumulated 대문자 텍스트를 가져오는 Result() string 메서드를 Add합니다.

    타입을 올바르게 Initialize하기 위해 NewCountingReader(source string) *CountingReaderNewUppercaseWriter() *UppercaseWriter constructor function을 만듭니다.

  • main.go: 표준 라이브러리와 함께 작동하는 사용자 지정 I/O 타입을 보여 줍니다.

    문자열 input을 읽은 다음, CountingReaderio.ReadAll을 사용하여 모든 data를 읽습니다. 해당 data를 UppercaseWriterWrite 메서드를 사용하여 전달합니다.

    결과를 다음 format으로 출력합니다:

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

다음 input이 제공됩니다:

  • 한 줄의 텍스트

예를 들어, 다음이 주어졌을 때:

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를 implements하기 때문에 io.ReadAll과 원활하게 작동한다는 점입니다. io.Reader를 허용하는 Go 생태계의 모든 function은 사용자 지정 타입과 함께 작동합니다. 이것이 interface 기반 설계의 powerful한 점입니다.

직접 해보기

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: [writer의 결과]
	// Bytes read: [총 바이트]

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

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

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

직접 연습해 보세요: 온라인 Go 컴파일러