課題10: インターフェースを使ったI/Oシステム
課題概要
インターフェースを使って、I/Oシステムを実装します。io.Reader、io.Writer、カスタムインターフェース、デコレーターパターン、モックを学びます。
マンダトリー要件
要件 1: カスタムReader実装
様々なReaderを実装してください。
ファイル: reader/custom_readers.go
package reader
import (
"io"
)
// StringReader は文字列から読み込む
type StringReader struct {
data string
pos int
}
// NewStringReader は StringReader を作成
func NewStringReader(data string) *StringReader {
// TODO: 実装してください
}
// Read は io.Reader を実装
func (sr *StringReader) Read(p []byte) (n int, err error) {
// TODO: 実装してください
// - data[pos:] から p にコピー
// - pos を更新
// - すべて読み終わったら io.EOF を返す
}
// LimitReader は最大バイト数まで読み込む
type LimitReader struct {
reader io.Reader
limit int64
read int64
}
// NewLimitReader は LimitReader を作成
func NewLimitReader(r io.Reader, limit int64) *LimitReader {
// TODO: 実装してください
}
// Read は io.Reader を実装
func (lr *LimitReader) Read(p []byte) (n int, err error) {
// TODO: 実装してください
// - limit まで読み込んだら io.EOF
// - 一度に読み込むサイズを制限
}
// RepeatReader は同じデータを繰り返し読み込む
type RepeatReader struct {
data []byte
count int // 残り繰り返し回数(-1は無限)
pos int
}
// NewRepeatReader は RepeatReader を作成
func NewRepeatReader(data []byte, count int) *RepeatReader {
// TODO: 実装してください
}
// Read は io.Reader を実装
func (rr *RepeatReader) Read(p []byte) (n int, err error) {
// TODO: 実装してください
// - count 回繰り返す(-1なら無限)
}
// MultiReader は複数のReaderを順番に読み込む
type MultiReader struct {
readers []io.Reader
current int
}
// NewMultiReader は MultiReader を作成
func NewMultiReader(readers ...io.Reader) *MultiReader {
// TODO: 実装してください
}
// Read は io.Reader を実装
func (mr *MultiReader) Read(p []byte) (n int, err error) {
// TODO: 実装してください
// - 現在のReaderから読み込み
// - EOFなら次のReaderに移動
}
要件2: カスタムWriter実装
様々なWriterを実装してください。
ファイル: writer/custom_writers.go
package writer
import (
"io"
)
// CountWriter は書き込みバイト数をカウント
type CountWriter struct {
writer io.Writer
count int64
}
// NewCountWriter は CountWriter を作成
func NewCountWriter(w io.Writer) *CountWriter {
// TODO: 実装してください
}
// Write は io.Writer を実装
func (cw *CountWriter) Write(p []byte) (n int, err error) {
// TODO: 実装してください
// - writer に書き込み
// - count を更新
}
// Count は書き込み総バイト数を返す
func (cw *CountWriter) Count() int64 {
// TODO: 実装してください
}
// PrefixWriter は各行にプレフィックスを追加
type PrefixWriter struct {
writer io.Writer
prefix string
atLineStart bool
}
// NewPrefixWriter は PrefixWriter を作成
func NewPrefixWriter(w io.Writer, prefix string) *PrefixWriter {
// TODO: 実装してください
}
// Write は io.Writer を実装
func (pw *PrefixWriter) Write(p []byte) (n int, err error) {
// TODO: 実装してください
// - 行の先頭にプレフィックスを追加
// - 改行を検出して次の行もプレフィックス付き
}
// TeeWriter は2つのWriterに同時に書き込む
type TeeWriter struct {
writer1 io.Writer
writer2 io.Writer
}
// NewTeeWriter は TeeWriter を作成
func NewTeeWriter(w1, w2 io.Writer) *TeeWriter {
// TODO: 実装してください
}
// Write は io.Writer を実装
func (tw *TeeWriter) Write(p []byte) (n int, err error) {
// TODO: 実装してください
// - 両方のWriterに書き込む
// - どちらかがエラーならエラーを返す
}
// MultiWriter は複数のWriterに同時に書き込む
type MultiWriter struct {
writers []io.Writer
}
// NewMultiWriter は MultiWriter を作成
func NewMultiWriter(writers ...io.Writer) *MultiWriter {
// TODO: 実装してください
}
// Write は io.Writer を実装
func (mw *MultiWriter) Write(p []byte) (n int, err error) {
// TODO: 実装してください
}
要件3: ストリーム処理パイプライン
ストリーム処理のパイプラインを実装してください。
ファイル: pipeline/pipeline.go
package pipeline
import (
"io"
)
// Processor はデータ変換インターフェース
type Processor interface {
Process(input io.Reader, output io.Writer) error
}
// UpperCaseProcessor は大文字に変換
type UpperCaseProcessor struct{}
func (p *UpperCaseProcessor) Process(input io.Reader, output io.Writer) error {
// TODO: 実装してください
}
// LowerCaseProcessor は小文字に変換
type LowerCaseProcessor struct{}
func (p *LowerCaseProcessor) Process(input io.Reader, output io.Writer) error {
// TODO: 実装してください
}
// ReverseProcessor は行を逆順にする
type ReverseProcessor struct{}
func (p *ReverseProcessor) Process(input io.Reader, output io.Writer) error {
// TODO: 実装してください
}
// FilterProcessor は条件に合う行のみ出力
type FilterProcessor struct {
predicate func(string) bool
}
func NewFilterProcessor(predicate func(string) bool) *FilterProcessor {
// TODO: 実装してください
}
func (p *FilterProcessor) Process(input io.Reader, output io.Writer) error {
// TODO: 実装してください
}
// Pipeline はプロセッサーのパイプライン
type Pipeline struct {
processors []Processor
}
// NewPipeline は Pipeline を作成
func NewPipeline() *Pipeline {
// TODO: 実装してください
}
// Add はプロセッサーを追加
func (p *Pipeline) Add(processor Processor) *Pipeline {
// TODO: 実装してください
}
// Execute はパイプラインを実行
func (p *Pipeline) Execute(input io.Reader, output io.Writer) error {
// TODO: 実装してください
// - 各プロセッサーを順番に実行
// - 中間結果は bytes.Buffer を使う
}
要件4: ファイル処理ツール
実用的なファイル処理ツールを実装してください。
ファイル: filetools/filetools.go
package filetools
import (
"io"
)
// Copy はファイルをコピー
func Copy(src, dst string) error {
// TODO: 実装してください
// - os.Open で src を開く
// - os.Create で dst を作成
// - io.Copy でコピー
// - defer でクローズ
}
// CopyWithProgress は進捗を表示してコピー
func CopyWithProgress(src, dst string, progress func(copied, total int64)) error {
// TODO: 実装してください
// - ファイルサイズを取得
// - コピーしながら progress を呼ぶ
}
// Cat は複数ファイルを連結
func Cat(output string, inputs ...string) error {
// TODO: 実装してください
// - output ファイルを作成
// - 各 input ファイルを順番にコピー
}
// Split はファイルを複数に分割
func Split(input string, chunkSize int64) ([]string, error) {
// TODO: 実装してください
// - chunkSize バイトごとに分割
// - input.part001, input.part002, ... というファイル名
// - 作成したファイル名のスライスを返す
}
// Grep は正規表現にマッチする行を抽出
func Grep(pattern, filename string) ([]string, error) {
// TODO: 実装してください
// - regexp.MustCompile でパターンをコンパイル
// - 各行をチェック
}
// LineCount はファイルの行数を返す
func LineCount(filename string) (int, error) {
// TODO: 実装してください
}
// Head は最初のn行を返す
func Head(filename string, n int) ([]string, error) {
// TODO: 実装してください
}
// Tail は最後のn行を返す
func Tail(filename string, n int) ([]string, error) {
// TODO: 実装してください
}
期待される出力
// StringReader
reader := NewStringReader("Hello, World!")
buf := make([]byte, 5)
n, _ := reader.Read(buf)
fmt.Println(string(buf[:n])) // "Hello"
// CountWriter
var buffer bytes.Buffer
cw := NewCountWriter(&buffer)
cw.Write([]byte("Hello"))
cw.Write([]byte("World"))
fmt.Println(cw.Count()) // 10
// Pipeline
pipeline := NewPipeline().
Add(&UpperCaseProcessor{}).
Add(NewFilterProcessor(func(line string) bool {
return len(line) > 5
}))
input := strings.NewReader("hello\nworld\ngo")
var output bytes.Buffer
pipeline.Execute(input, &output)
fmt.Println(output.String()) // "HELLO\nWORLD\n"
// ファイルコピー
err := Copy("source.txt", "dest.txt")
// 進捗付きコピー
err = CopyWithProgress("large.dat", "copy.dat", func(copied, total int64) {
fmt.Printf("\rProgress: %d/%d (%.1f%%)",
copied, total, float64(copied)/float64(total)*100)
})
ボーナス課題
ボーナス1: 圧縮/解凍Reader/Writer
package compress
import (
"compress/gzip"
"io"
)
// CompressWriter は gzip 圧縮して書き込む
type CompressWriter struct {
writer io.Writer
gzipWriter *gzip.Writer
}
// NewCompressWriter は CompressWriter を作成
func NewCompressWriter(w io.Writer) *CompressWriter {
// TODO: 実装してください
}
// Write は io.Writer を実装
func (cw *CompressWriter) Write(p []byte) (n int, err error) {
// TODO: 実装してください
}
// Close はリソースを解放
func (cw *CompressWriter) Close() error {
// TODO: 実装してください
}
// DecompressReader は gzip 解凍して読み込む
type DecompressReader struct {
reader io.Reader
gzipReader *gzip.Reader
}
// NewDecompressReader は DecompressReader を作成
func NewDecompressReader(r io.Reader) (*DecompressReader, error) {
// TODO: 実装してください
}
// Read は io.Reader を実装
func (dr *DecompressReader) Read(p []byte) (n int, err error) {
// TODO: 実装してください
}
// Close はリソースを解放
func (dr *DecompressReader) Close() error {
// TODO: 実装してください
}
ボーナス2: 暗号化Reader/Writer
package crypto
import (
"crypto/aes"
"crypto/cipher"
"io"
)
// EncryptWriter は暗号化して書き込む
type EncryptWriter struct {
writer io.Writer
stream cipher.Stream
}
// NewEncryptWriter は EncryptWriter を作成
func NewEncryptWriter(w io.Writer, key []byte) (*EncryptWriter, error) {
// TODO: 実装してください
// - AES暗号を初期化
// - CTRモードでストリーム暗号を作成
}
// Write は io.Writer を実装
func (ew *EncryptWriter) Write(p []byte) (n int, err error) {
// TODO: 実装してください
}
// DecryptReader は復号化して読み込む
type DecryptReader struct {
reader io.Reader
stream cipher.Stream
}
// NewDecryptReader は DecryptReader を作成
func NewDecryptReader(r io.Reader, key []byte) (*DecryptReader, error) {
// TODO: 実装してください
}
// Read は io.Reader を実装
func (dr *DecryptReader) Read(p []byte) (n int, err error) {
// TODO: 実装してください
}
ボーナス3: HTTP ストリーミング
package httpstream
import (
"io"
"net/http"
)
// StreamDownload はHTTPからストリーミングダウンロード
func StreamDownload(url, filename string, progress func(downloaded, total int64)) error {
// TODO: 実装してください
// - http.Get でダウンロード開始
// - Content-Length ヘッダーから総サイズ取得
// - io.Copy でストリーミング保存
// - 定期的に progress を呼ぶ
}
// StreamUpload はHTTPにストリーミングアップロード
func StreamUpload(url, filename string, progress func(uploaded, total int64)) error {
// TODO: 実装してください
}
// Proxy は HTTP リクエストをプロキシ
func Proxy(target string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// TODO: 実装してください
// - target にリクエストを転送
// - レスポンスをストリーミングで返す
}
}
評価基準
| 項目 | 配点 | 詳細 |
|---|---|---|
| カスタムReader | 25点 | 各Readerが正しく実装されている |
| カスタムWriter | 25点 | 各Writerが正しく実装されている |
| パイプライン | 25点 | ストリーム処理が動作する |
| ファイルツール | 25点 | 実用的なツールが実装されている |
| **ボーナス1** | 10点 | 圧縮/解凍が実装されている |
| **ボーナス2** | 10点 | 暗号化/復号化が実装されている |
| **ボーナス3** | 5点 | HTTPストリーミングが実装されている |
提出方法
submission/
├── go.mod
├── reader/
│ ├── custom_readers.go
│ └── custom_readers_test.go
├── writer/
│ ├── custom_writers.go
│ └── custom_writers_test.go
├── pipeline/
│ ├── pipeline.go
│ └── pipeline_test.go
├── filetools/
│ ├── filetools.go
│ └── filetools_test.go
└── bonus/
├── compress/
├── crypto/
└── httpstream/
ヒント
- Reader: bufio.Readerを使うと便利
- Writer: bytes.Bufferで中間バッファ
- Pipeline: io.Pipeでストリーミング接続
- コピー: io.Copyを活用
- 進捗: io.TeeReaderでバイト数をカウント
- 暗号化: crypto/aes と crypto/cipher を使う
テストケース
// reader/custom_readers_test.go
func TestStringReader(t *testing.T) {
reader := NewStringReader("Hello")
buf := make([]byte, 3)
n, err := reader.Read(buf)
if n != 3 || err != nil {
t.Errorf("Read() = %d, %v; want 3, nil", n, err)
}
if string(buf) != "Hel" {
t.Errorf("Read data = %q; want %q", string(buf), "Hel")
}
n, err = reader.Read(buf)
if n != 2 || err != io.EOF {
t.Errorf("Read() = %d, %v; want 2, EOF", n, err)
}
}
// writer/custom_writers_test.go
func TestCountWriter(t *testing.T) {
var buf bytes.Buffer
cw := NewCountWriter(&buf)
cw.Write([]byte("Hello"))
cw.Write([]byte("World"))
if cw.Count() != 10 {
t.Errorf("Count() = %d; want 10", cw.Count())
}
if buf.String() != "HelloWorld" {
t.Errorf("Written data = %q; want %q", buf.String(), "HelloWorld")
}
}
// pipeline/pipeline_test.go
func TestPipeline(t *testing.T) {
input := strings.NewReader("hello\nworld")
var output bytes.Buffer
pipeline := NewPipeline().
Add(&UpperCaseProcessor{})
err := pipeline.Execute(input, &output)
if err != nil {
t.Fatalf("Execute() error = %v", err)
}
expected := "HELLO\nWORLD"
if output.String() != expected {
t.Errorf("Output = %q; want %q", output.String(), expected)
}
}
学習リソース
まとめ
この課題を完了すると、以下ができるようになります:
- io.Reader/Writerインターフェースの実装
- デコレーターパターンの活用
- ストリーム処理のパイプライン構築
- 実用的なファイル処理ツールの作成
Go Foundations コース完了おめでとうございます!