暗黙的な実装
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のコードは疎結合で柔軟に保たれます。型は事前にインターフェースについて知る必要がないため、独立したパーツからシステムを構成することが容易になります。
チャレンジ
簡単Go において型がどのようにインターフェースを暗黙的に満たすかを示す、メッセージ配信システムを構築しましょう。その関係を明示的に宣言することなく、すべてが同じインターフェースを満たす異なるメッセンジャー型を作成します。
コードは 2 つのファイルに分けて構成します:
messengers.go:Send(message string) stringという単一のメソッドを持つMessengerインターフェースを定義します。次に、このインターフェースを暗黙的に満たす 2 つの構造体を作成します:Addressフィールド(string)を持つEmailMessengerPhoneNumberフィールド(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.com、555-1234、Hello! が与えられた場合、出力は以下のようになります:
Email to alice@example.com: Hello!
SMS to 555-1234: Hello!ここでの重要な洞察は、DeliverMessage 関数が任意の Messenger を受け入れ、EmailMessenger と SMSMessenger の両方が、一致する 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 を使用して各メッセンジャー経由でメッセージを送信し、
// その結果をプリントしてください
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー