インターフェース
レッスン9:インターフェース
現実世界のアナロジー
レストランで料理を注文する場面を想像してください。シェフが誰か、どんなフライパンを使っているか、どのように調理しているかを知る必要はありません — メニューを見て「エビチリ」を注文するだけです。メニューはインターフェースです。「何ができるか」を定義し、「誰がやるか」「どうやるか」は気にしません。
Goのインターフェースも同じように動作します。メソッドシグネチャの集合を定義し、これらのメソッドを実装するすべての型が自動的にインターフェースを満たします — 明示的な宣言は不要です。
コアコンセプト
| コンセプト | 説明 |
|---|---|
| インターフェース | メソッドシグネチャの集合で、振る舞いの契約を定義 |
| 暗黙の実装 | 型がインターフェースのすべてのメソッドを実装していれば、自動的にインターフェースを満たす |
| ダックタイピング | 「アヒルのように歩き、アヒルのように鳴くなら、それはアヒル」 |
空のインターフェース interface{} |
メソッドを含まない。すべての型がこれを満たす |
| 型アサーション | インターフェース値から具象型を抽出する |
| インターフェースの合成 | 複数のインターフェースを埋め込んで大きなインターフェースを構築 |
基本構文と使い方
インターフェースの定義
// Speaker インターフェースを定義
type Speaker interface {
Speak() string
}
暗黙の実装
// Dog 型は Speaker インターフェースを実装(宣言は不要)
type Dog struct {
Name string
}
func (d Dog) Speak() string {
return "ワン!私は" + d.Name + "です"
}
// Cat 型も Speaker インターフェースを実装
type Cat struct {
Name string
}
func (c Cat) Speak() string {
return "ニャー!私は" + c.Name + "です"
}
implements キーワードがありません。型がインターフェースが必要とするすべてのメソッドを持っていれば、自動的にそのインターフェースを実装します。
インターフェースの使用
func makeItSpeak(s Speaker) {
fmt.Println(s.Speak())
}
func main() {
dog := Dog{Name: "Rex"}
cat := Cat{Name: "Whiskers"}
makeItSpeak(dog) // 出力: ワン!私はRexです
makeItSpeak(cat) // 出力: ニャー!私はWhiskersです
}
空のインターフェース interface{}
// 空のインターフェースは任意の型の値を保持できる
func printAnything(v interface{}) {
fmt.Printf("値: %v、型: %T\n", v, v)
}
func main() {
printAnything(42) // 値: 42、型: int
printAnything("hello") // 値: hello、型: string
printAnything(3.14) // 値: 3.14、型: float64
}
interface{} は any に短縮できます。両者は等価です。
型アサーションと型スイッチ
func describe(v interface{}) {
// 型アサーション:インターフェース値を具象型に変換を試みる
str, ok := v.(string)
if ok {
fmt.Println("これは文字列です:", str)
return
}
// 型スイッチ:複数の型を优雅に処理
switch val := v.(type) {
case int:
fmt.Println("これは整数です:", val)
case float64:
fmt.Println("これは浮動小数点数です:", val)
case bool:
fmt.Println("これはブール値です:", val)
default:
fmt.Printf("不明な型: %T\n", val)
}
}
v.(Type) はアサーションが失敗するとpanicを起こしますが、v, ok := v.(Type) は安全にゼロ値と false を返します。
インターフェースの合成
// ベースインターフェース
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// 合成インターフェース:複数のインターフェースを埋め込み
type ReadWriter interface {
Reader
Writer
}
// ReadWriter は Read と Write の両方のメソッドの実装が必要
io.Reader、io.Writer、fmt.Stringer などです。
サンプルコード
例:図形の面積計算(難易度⭐)
package main
import (
"fmt"
"math"
)
// Shape インターフェースは「図形」の振る舞いを定義
type Shape interface {
Area() float64
Perimeter() float64
}
// Rectangle(長方形)
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// Circle(円)
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
// printShapeInfo は Shape インターフェースの任意の実装を受け取る
func printShapeInfo(s Shape) {
fmt.Printf("面積: %.2f、周囲の長さ: %.2f\n", s.Area(), s.Perimeter())
}
func main() {
rect := Rectangle{Width: 10, Height: 5}
circle := Circle{Radius: 7}
fmt.Print("長方形 -> ")
printShapeInfo(rect)
fmt.Print("円 -> ")
printShapeInfo(circle)
}
長方形 -> 面積: 50.00、周囲の長さ: 30.00
円 -> 面積: 153.94、周囲の長さ: 43.98
例:インターフェースのスライスとソート(難易度⭐⭐)
package main
import (
"fmt"
"sort"
)
// Employee インターフェース
type Employee interface {
Name() string
Salary() float64
}
// FullTime は正社員
type FullTime struct {
name string
annual float64 // 年間給与
}
func (f FullTime) Name() string { return f.name }
func (f FullTime) Salary() float64 { return f.annual }
// Contractor は契約社員
type Contractor struct {
name string
hourly float64 // 時給
hours float64 // 労働時間
}
func (c Contractor) Name() string { return c.name }
func (c Contractor) Salary() float64 { return c.hourly * c.hours }
// BySalary は sort.Interface を実装し、給与でソートする
type BySalary []Employee
func (s BySalary) Len() int { return len(s) }
func (s BySalary) Less(i, j int) bool { return s[i].Salary() < s[j].Salary() }
func (s BySalary) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// totalCost は総人件費を計算する
func totalCost(employees []Employee) float64 {
total := 0.0
for _, e := range employees {
total += e.Salary()
}
return total
}
func main() {
team := []Employee{
FullTime{name: "Zhang San", annual: 120000},
Contractor{name: "Li Si", hourly: 200, hours: 1000},
FullTime{name: "Wang Wu", annual: 150000},
Contractor{name: "Zhao Liu", hourly: 180, hours: 800},
}
fmt.Println("=== 給与ソート前 ===")
for _, e := range team {
fmt.Printf(" %s: $%.0f\n", e.Name(), e.Salary())
}
sort.Sort(BySalary(team))
fmt.Println("\n=== 給与ソート後 ===")
for _, e := range team {
fmt.Printf(" %s: $%.0f\n", e.Name(), e.Salary())
}
fmt.Printf("\n総人件費: $%.0f\n", totalCost(team))
}
=== 給与ソート前 ===
Zhang San: $120000
Li Si: $200000
Wang Wu: $150000
Zhao Liu: $144000
=== 給与ソート後 ===
Zhang San: $120000
Zhao Liu: $144000
Wang Wu: $150000
Li Si: $200000
総人件費: $614000
例:io.Reader/Writerインターフェースの実装(難易度⭐⭐⭐)
package main
import (
"fmt"
"io"
"strings"
)
// UpperReader は読み込んだ内容を大文字に変換する
type UpperReader struct {
source io.Reader
}
// io.Reader インターフェースを実装
func (u *UpperReader) Read(p []byte) (n int, err error) {
n, err = u.source.Read(p)
// 読み込んだすべてのバイトを大文字に変換
for i := 0; i < n; i++ {
if p[i] >= 'a' && p[i] <= 'z' {
p[i] = p[i] - 32 // ASCII: 小文字を大文字に
}
}
return
}
// UpperReader のコンストラクタ
func NewUpperReader(r io.Reader) *UpperReader {
return &UpperReader{source: r}
}
// PrefixWriter は各書き込みの前にプレフィックスを追加する
type PrefixWriter struct {
prefix string
target io.Writer
}
// io.Writer インターフェースを実装
func (p *PrefixWriter) Write(data []byte) (n int, err error) {
// まずプレフィックスを書き込む
_, err = p.target.Write([]byte(p.prefix))
if err != nil {
return 0, err
}
// 次に実際のデータを書き込む
return p.target.Write(data)
}
// PrefixWriter のコンストラクタ
func NewPrefixWriter(prefix string, w io.Writer) *PrefixWriter {
return &PrefixWriter{prefix: prefix, target: w}
}
// TeeReader は読み込みと同時に書き込む(teeコマンドに類似)
func TeeReader(r io.Reader, w io.Writer) io.Reader {
return &teeReader{r: r, w: w}
}
type teeReader struct {
r io.Reader
w io.Writer
}
func (t *teeReader) Read(p []byte) (n int, err error) {
n, err = t.r.Read(p)
if n > 0 {
// 読み込みながら w にも書き込む
t.w.Write(p[:n])
}
return
}
func main() {
fmt.Println("=== UpperReader の例 ===")
// 文字列から Reader を作成
source := strings.NewReader("hello, go interfaces!")
upper := NewUpperReader(source)
// io.ReadAll ですべてのコンテンツを読み込む
buf := make([]byte, 64)
n, _ := upper.Read(buf)
fmt.Printf("大文字変換結果: %s\n", string(buf[:n]))
fmt.Println("\n=== PrefixWriter の例 ===")
// プレフィックス付きでstdoutに書き込む
writer := NewPrefixWriter("[LOG] ", &strings.Builder{})
writer.Write([]byte("システムが起動しました\n"))
// strings.Builder を使って出力をキャプチャ
var builder strings.Builder
pw := NewPrefixWriter("[DEBUG] ", &builder)
pw.Write([]byte("インターフェースが初期化されました"))
fmt.Println(builder.String())
fmt.Println("\n=== TeeReader の例 ===")
// 読み込みながら別のWriterにも書き込む
input := strings.NewReader("Go is powerful")
var capture strings.Builder
tee := TeeReader(input, &capture)
buf2 := make([]byte, 1024)
n2, _ := tee.Read(buf2)
fmt.Printf("読み込み: %s\n", string(buf2[:n2]))
fmt.Printf("キャプチャされた内容: %s\n", capture.String())
}
=== UpperReader の例 ===
大文字変換結果: HELLO, GO INTERFACES!
=== PrefixWriter の例 ===
[DEBUG] インターフェースが初期化されました
=== TeeReader の例 ===
読み込み: Go is powerful
キャプチャされた内容: Go is powerful
現実世界の使用場面
場面1:ログシステム(ストラテジーパターン)
package main
import (
"fmt"
"os"
"time"
)
// Logger はログ出力インターフェース
type Logger interface {
Log(message string)
}
// ConsoleLogger はコンソールログ出力
type ConsoleLogger struct{}
func (c ConsoleLogger) Log(message string) {
timestamp := time.Now().Format("2006-01-02 15:04:05")
fmt.Printf("[%s] %s\n", timestamp, message)
}
// FileLogger はファイルログ出力
type FileLogger struct {
file *os.File
}
func NewFileLogger(filename string) (*FileLogger, error) {
f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return nil, err
}
return &FileLogger{file: f}, nil
}
func (f *FileLogger) Log(message string) {
timestamp := time.Now().Format("2006-01-02 15:04:05")
fmt.Fprintf(f.file, "[%s] %s\n", timestamp, message)
}
// MultiLogger は複数のロガーに同時出力する
type MultiLogger struct {
loggers []Logger
}
func (m *MultiLogger) Add(l Logger) {
m.loggers = append(m.loggers, l)
}
func (m *MultiLogger) Log(message string) {
for _, l := range m.loggers {
l.Log(message)
}
}
// App は Logger インターフェースを使用し、具体的な実装を気にしない
type App struct {
logger Logger
}
func (a *App) Run() {
a.logger.Log("アプリケーションが起動しました")
a.logger.Log("リクエストを処理中...")
a.logger.Log("リクエスト処理が完了しました")
}
func main() {
// 複数のログ出力を組み合わせ
multi := &MultiLogger{}
multi.Add(ConsoleLogger{})
// ログ出力方法を簡単に切り替えたり追加したりできる
app := &App{logger: multi}
app.Run()
}
[2026-06-26 10:30:00] アプリケーションが起動しました
[2026-06-26 10:30:00] リクエストを処理中...
[2026-06-26 10:30:00] リクエスト処理が完了しました
場面2:データストレージの抽象化レイヤー
package main
import "fmt"
// Store はストレージインターフェース
type Store interface {
Get(key string) (string, bool)
Set(key string, value string)
Delete(key string)
Keys() []string
}
// MemoryStore はインメモリストレージの実装
type MemoryStore struct {
data map[string]string
}
func NewMemoryStore() *MemoryStore {
return &MemoryStore{data: make(map[string]string)}
}
func (m *MemoryStore) Get(key string) (string, bool) {
val, ok := m.data[key]
return val, ok
}
func (m *MemoryStore) Set(key string, value string) {
m.data[key] = value
}
func (m *MemoryStore) Delete(key string) {
delete(m.data, key)
}
func (m *MemoryStore) Keys() []string {
keys := make([]string, 0, len(m.data))
for k := range m.data {
keys = append(keys, k)
}
return keys
}
// CacheService は Store インターフェースを使用し、具体的なストレージから分離されている
type CacheService struct {
store Store
}
func (c *CacheService) GetOrSet(key, defaultValue string) string {
if val, ok := c.store.Get(key); ok {
return val
}
c.store.Set(key, defaultValue)
return defaultValue
}
func (c *CacheService) GetAll() map[string]string {
result := make(map[string]string)
for _, key := range c.store.Keys() {
if val, ok := c.store.Get(key); ok {
result[key] = val
}
}
return result
}
func main() {
// インメモリストレージを使用
store := NewMemoryStore()
cache := &CacheService{store: store}
// データを書き込む
cache.GetOrSet("user:1", "Alice")
cache.GetOrSet("user:2", "Bob")
cache.GetOrSet("config:theme", "dark")
// データを読み込む
fmt.Println("すべてのキャッシュデータ:")
for k, v := range cache.GetAll() {
fmt.Printf(" %s = %s\n", k, v)
}
// GetOrSet のテスト:既存のキーは古い値を返す
result := cache.GetOrSet("user:1", "Charlie")
fmt.Printf("\nuser:1 の値: %s\n", result)
}
すべてのキャッシュデータ:
user:1 = Alice
user:2 = Bob
config:theme = dark
user:1 の値: Alice
📖 まとめ
| 重要ポイント | 説明 |
|---|---|
| インターフェースは振る舞いを定義 | 「何ができるか」だけを気にし、「何であるか」は気にしない |
| 暗黙の実装 | 宣言は不要。メソッドを実装すればインターフェースを満たす |
空のインターフェース any |
任意の型の値を保持できる |
| 型アサーション | インターフェース値から具象型を抽出。安全のため comma ok パターンを使用 |
| インターフェースの合成 | 小さなインターフェースを埋め込んで大きなインターフェースを構築 |
| インターフェースに基づいてプログラミング | 具象実装ではなくインターフェースに依存することで柔軟性を実現 |
❓ よくある質問
質問1:インターフェースと構造体の違いは?
構造体は「何であるか」を定義する具象データ型です。インターフェースは「何ができるか」を定義する振る舞いの契約です。構造体はインスタンス化できますが、インターフェースは直接インスタンス化することはできず、インターフェースを満たす任意の型の値を保持できます。
質問2:なぜGoには implements キーワードがいらないの?
Goはダックタイピングの設計を採用しています。コンパイラはコンパイル時に型がインターフェースを満たすかどうかを自動的にチェックします。この設計により、インターフェースと実装は完全に分離されます — 既存のコードを変更せずに、サードパーティライブラリの型に新しいインターフェースを定義できます。
質問3:いつインターフェースを定義すべき?
- 多態性が必要なとき(同じ関数で異なる型を処理)
- 分離が必要なとき(具象実装ではなく抽象に依存)
- モックテストが必要なとき(インターフェースを使って実際の依存を置き換える)
質問4:interface{} と any の違いは?
ありません。any はGo 1.18で導入された interface{} の型エイリアスです。完全に等価です。簡潔なので any が推奨されます。
📝 演習
演習1:基礎 — Stringerインターフェースの実装
fmt.Stringer インターフェースは String() string メソッドのみを持ちます。fmt.Println や %v フォーマットを使用する際、Goが自動的にこのメソッドを呼び出します。
// 以下の型に対して fmt.Stringer インターフェースを実装してください
type Temperature struct {
Celsius float64
}
type Money struct {
Amount float64
Currency string
}
// 期待される動作:
// fmt.Println(Temperature{36.5}) -> "36.5°C"
// fmt.Println(Money{99.9, "USD"}) -> "$99.90"
演習2:中級 — 通知システムの設計
複数の通知方法をサポートする通知システムを設計してください:
// 1. Notifier インターフェースを定義
// 2. EmailNotifier、SMSNotifier、WechatNotifier を実装
// 3. 複数の Notifier に同時に送信できる関数を実装
// 4. インターフェースのスライスを使って異なる Notifier を格納
演習3:チャレンジ — 簡易プラグインシステムの実装
// Name()、Version()、Execute() メソッドを持つ Plugin インターフェースを定義
// 少なくとも3つの異なる Plugin を実装
// プラグインの登録、検索、実行ができる PluginManager を作成
// ヒント:map[string]Plugin を使ってプラグインを格納
次のレッスン
インターフェースはGoにおける多態性の基盤です。インターフェースをマスターすれば、柔軟でテスト可能、拡張可能なコードを書くことができます。次は、Goのエラー処理メカニズムについて学びます — Go言語で最も重要な設計哲学の一つです。



