Menu
Coddy logo textTech

io.Reader と io.Writer

CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 73/107。

Goの標準ライブラリは、2つの基本的なinterfacesである io.Readerio.Writer を通じて、インターフェースベースの設計を示しています。これらのシンプルなinterfacesは、ファイル操作からネットワーク通信まで、あらゆる機能を支えています。

io.Readerインターフェースに必要なメソッドは1つだけです。

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

スライスに最大で len(p) バイトを読み込み、読み込んだバイト数を返します。同様に、io.Writer は次のように定義されています。

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

これらのメソッドを implementing する任意の型は、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.Writerinterfaces を実装するカスタムテキストストリームプロセッサを作成しましょう!テキストがその中を流れる際に変換できる型を作成し、これらの基本的な interfaces によって powerful な I/O 合成がどのように可能になるかを示します。

コードは2つのファイルに分けて整理します。

  • streams.go:カスタム reader と writer の型を定義します。

    文字列をラップし、読み取られた bytes 数を追跡する CountingReader 構造体を作成します。source 文字列用のフィールドと position tracker を持つ必要があります。指定されたスライスに source から bytes を読み込み、position を更新し、source をすべて読み終えたら io.EOF を返す Read(p []byte) (int, error) メソッドを実装します。

    書き込まれた data を収集し、大文字に変換する UppercaseWriter 構造体を作成します。accumulated result を内部に保存する必要があります。incoming bytes を大文字に変換して保存する Write(p []byte) (int, error) メソッドを実装します。accumulated uppercase text を取得するための Result() string メソッドを追加します。

    型を適切に初期化するために、コンストラクター関数 NewCountingReader(source string) *CountingReaderNewUppercaseWriter() *UppercaseWriter を作成します。

  • main.go:標準ライブラリと連携するカスタム I/O 型の動作を示します。

    文字列 input を読み込み、次に io.ReadAllCountingReader を使用してすべての data を読み取ります。その data を Write メソッドで UppercaseWriter に渡します。

    結果を次の形式で出力します。

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

次の input が提供されます。

  • 1行のテキスト

たとえば、次の値が与えられた場合:

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 とシームレスに連携できることです。io.Reader を受け取る Go エコシステム内の任意の関数が、カスタム型である CountingReader と連携して動作します。これが interface ベースの設計の力です。

自分で試してみよう

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: [ライターからの結果]
	// Bytes read: [合計バイト数]

	// 未使用のインポートエラーを避けるためのプレースホルダー - 実装時に削除する
	_ = io.EOF
	fmt.Println("Original:", input)
	// fmt.Println("Uppercase:", upperWriter.Result())
	// fmt.Println("Bytes read:", countingReader.Position)
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Goオンラインコンパイラ