Goのioパッケージに学ぶ抽象化

2025/07/05

Go

interface

Goのinterfaceは,メソッドの集合を定義する型です.ある型がinterfaceで定義されたすべてのメソッドを実装していれば,その型はそのinterfaceを満たすと言います.

// グラディエーターインターフェース
type Gladiator interface {
    Attack() int        // 攻撃力を返す
    GetWeapon() string  // 武器名を返す
}

// 剣使いの構造体
type Swordsman struct {
    name string
}

func (s Swordsman) Attack() int {
    return 2
}

func (s Swordsman) GetWeapon() string {
    return "剣"
}

// 斧使いの構造体
type Axeman struct {
    name string
}

func (a Axeman) Attack() int {
    return 3
}

func (a Axeman) GetWeapon() string {
    return "斧"
}

// 闘技場での戦い
func arenaFight(g Gladiator) {
    fmt.Printf("%s使いの攻撃!ダメージ: %d\n", g.GetWeapon(), g.Attack())
}

// 使用例
func main() {
    swordsman := Swordsman{name: "ケンタロウ"}
    axeman := Axeman{name: "オノマル"}
    
    arenaFight(swordsman)  // 剣使いの攻撃!ダメージ: 2
    arenaFight(axeman)     // 斧使いの攻撃!ダメージ: 3
}

SwordsmanやAxemanは明示的にGradiatorを実装しているわけではありませんが,Attack()GetWeapon()メソッドを持っているのでinterfaceを満たしていると言えます.

Everything is a file

Unix系のOSにおいて,「Everything is a file」という哲学があります.これは,システム内のあらゆるリソースを統一的な方法で扱うという考え方です.より具体的には,通常のファイルだけでなく,以下のようなものも全て「ファイル」として扱います.

  • ディレクトリ (/home/user)
  • デバイス (/dev/sda1, /dev/null)
  • プロセス情報 (/proc/1234/status)
  • ネットワークソケット
  • パイプ

これらは全て同じシステムコールreadwriteでアクセスできます.

# 通常のファイルを読む
cat file.txt

# ネットワークからのレスポンスを読み込む
curl https://example.com

# 読み込んだ内容をファイルに保存(読み書き)
curl https://example.com > index.html

この統一性により.プログラマーは「読み書きできるもの」として単純に考えることができます.

ioパッケージ

Goのioパッケージはこの「Everything is a file」の思想を体現したものです(多分).

その代表がReaderインターフェースとWriterインターフェースです.

io.Reader

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

「読み込み可能なもの」です.Readは,与えられたバイトスライスpに最大len(p)バイトを読み込み,実際に読み込んだバイト数nと発生したエラーerrを返します.ファイルの終端に達した場合,Read0, io.EOFを返します.

io.Writer

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

「書き込み可能なもの」です.Writeは,与えられたバイトスライスpのデータを書き込み先に書き込み,実際に書き込んだバイト数nと発生したエラーerrを返します.

これらのインターフェースは,以下のような多くの型で実装されています.

  • os.File(ファイル)
  • bytes.Buffer(メモリバッファ)
  • net.Conn(ネットワーク接続)

コード例

以下の関数copyDataは,io.Readerからio.Writerへデータをコピーするだけですが,ファイル,ネットワーク,バッファなどあらゆる読み書き対象で使い回すことができます.

package main

import (
	"io"
	"net/http"
	"os"
)

// src(入力)からdst(出力)へコピー
func copyData(dst io.Writer, src io.Reader) {
	io.Copy(dst, src)
}

func main() {
	// ファイルをコピー
	file1, _ := os.Open("input.txt")
	file2, _ := os.Create("output.txt")
	copyData(file2, file1)
	file1.Close()
	file2.Close()

	// ネットワークからファイルへ
	resp, _ := http.Get("https://example.com/data")
	file3, _ := os.Create("download.txt")
	copyData(file3, resp.Body)
	resp.Body.Close()
	file3.Close()
}