Menu
Coddy logo textTech

暗黙的な実装

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

JavaやC#のような多くのオブジェクト指向言語では、implementsのようなキーワードを使用して、型がインターフェースを実装することを明示的に宣言する必要があります。Goは異なるアプローチを取ります。インターフェースの実装は暗黙的です。

型は、そのすべてのメソッドを実装するだけでインターフェースを満たします。その関係をどこかで宣言する必要はありません。コンパイラが自動的にそれを判別します。

type Writer interface {
    Write(data string) int
}

type FileWriter struct {
    Filename string
}

// FileWriterは暗黙的にWriterを実装します
func (f FileWriter) Write(data string) int {
    fmt.Println("Writing to", f.Filename)
    return len(data)
}

FileWriter には implements Writer 宣言がないことに注目してください。FileWriter はインターフェースのシグネチャに一致する Write(data string) int メソッドを持っているため、自動的に Writer インターフェースを満たします。

この設計には強力な利点があります。型が既に存在した後からインターフェースを定義できるのです。サードパーティのライブラリに、必要なメソッドを持つ型がある場合、元のコードを修正することなく、その型が既に満たしているインターフェースを作成できます。

func Save(w Writer, content string) {
    w.Write(content)
}

func main() {
    fw := FileWriter{Filename: "data.txt"}
    Save(fw, "Hello!")  // FileWriterがWriterを満たしているため動作します
}

暗黙的な実装により、Goのコードは疎結合で柔軟に保たれます。型は事前にインターフェースについて知る必要がないため、独立したパーツからシステムを構成することが容易になります。

challenge icon

チャレンジ

簡単

Go において型がどのようにインターフェースを暗黙的に満たすかを示す、メッセージ配信システムを構築しましょう。その関係を明示的に宣言することなく、すべてが同じインターフェースを満たす異なるメッセンジャー型を作成します。

コードは 2 つのファイルに分けて構成します:

  • messengers.go: Send(message string) string という単一のメソッドを持つ Messenger インターフェースを定義します。次に、このインターフェースを暗黙的に満たす 2 つの構造体を作成します:
    • Address フィールド(string)を持つ EmailMessenger
    • PhoneNumber フィールド(string)を持つ SMSMessenger
    各構造体は、配信内容を説明する文字列を返す Send メソッドを持つ必要があります。どちらの構造体も Messenger を実装していることを明示的に宣言してはいけません。Go はメソッドのシグネチャに基づいて自動的にそれを判断します。
  • main.go: 任意の Messenger とメッセージ文字列を受け取り、Send を呼び出した結果を返す DeliverMessage という関数を作成します。入力から連絡先情報を読み取り、両方のメッセンジャー型を作成し、その関数を使用してそれぞれのメッセンジャーでメッセージを配信します。

以下の入力が提供されます:

  • 1行目: メールアドレス
  • 2行目: 電話番号
  • 3行目: 送信するメッセージ

Send メソッドは、以下の形式の文字列を返す必要があります:

  • EmailMessenger: "Email to %s: %s" (Email to [Address]: [message])
  • SMSMessenger: "SMS to %s: %s" (SMS to [PhoneNumber]: [message])

例えば、alice@example.com555-1234Hello! が与えられた場合、出力は以下のようになります:

Email to alice@example.com: Hello!
SMS to 555-1234: Hello!

ここでの重要な洞察は、DeliverMessage 関数が任意の Messenger を受け入れ、EmailMessengerSMSMessenger の両方が、一致する Send メソッドを持っているだけでそのインターフェースを満たしているということです。どこにも implements キーワードはありません。Go の暗黙的な実装がすべてを処理します。

チートシート

Goでは、インターフェースの実装は暗黙的です。型がインターフェースのすべてのメソッドを実装するだけで、そのインターフェースを満たすことになります。明示的な宣言は必要ありません。

type Writer interface {
    Write(data string) int
}

type FileWriter struct {
    Filename string
}

// FileWriterは暗黙的にWriterを実装しています
func (f FileWriter) Write(data string) int {
    fmt.Println("Writing to", f.Filename)
    return len(data)
}

implements Writer という宣言はありません。FileWriter がインターフェースのシグネチャに一致する Write(data string) int メソッドを持っているため、自動的に Writer インターフェースを満たします。

型がインターフェースのことを知らなくても、そのインターフェースを満たすすべての型を使用できます。

func Save(w Writer, content string) {
    w.Write(content)
}

func main() {
    fw := FileWriter{Filename: "data.txt"}
    Save(fw, "Hello!")  // FileWriterがWriterを満たしているため動作します
}

この設計により、型が既に存在した後にインターフェースを定義することができ、コードの結合度を下げ、柔軟性を高めることができます。

自分で試してみよう

package main

import (
	"bufio"
	"fmt"
	"os"
)

// TODO: 任意の Messenger とメッセージ文字列を受け取り、
// Send を呼び出した結果を返す DeliverMessage 関数を作成してください

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// メールアドレスを読み込む
	email, _ := reader.ReadString('\n')
	email = email[:len(email)-1]
	
	// 電話番号を読み込む
	phone, _ := reader.ReadString('\n')
	phone = phone[:len(phone)-1]
	
	// メッセージを読み込む
	message, _ := reader.ReadString('\n')
	if len(message) > 0 && message[len(message)-1] == '\n' {
		message = message[:len(message)-1]
	}
	
	// TODO: メールアドレスを使用して EmailMessenger を作成してください
	
	// TODO: 電話番号を使用して SMSMessenger を作成してください
	
	// TODO: DeliverMessage を使用して各メッセンジャー経由でメッセージを送信し、
	// その結果をプリントしてください
}
quiz icon腕試し

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

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