Goにおけるダックタイピング
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 42/107。
ダックタイピングという用語は、「アヒルのように歩き、アヒルのように鳴くなら、それはアヒルだ」ということわざに由来します。Goでは、型がインターフェースを実装していることを明示的に宣言する必要はありません。適切なメソッドを持っていればよいのです。
このインターフェースと、まったく関係のない2つの型について考えてみましょう。
type Quacker interface {
Quack() string
}
type Duck struct{}
func (d Duck) Quack() string { return "Quack!" }
type Robot struct{}
func (r Robot) Quack() string { return "Beep-quack!" }
Duck も Robot も、それぞれの定義のどこにも Quacker について言及していません。それでも、正しいシグネチャを持つ Quack() メソッドがあるというだけで、どちらもインターフェースを満たします。
func MakeItQuack(q Quacker) {
fmt.Println(q.Quack())
}
func main() {
MakeItQuack(Duck{}) // Quack!
MakeItQuack(Robot{}) // Beep-quack!
}
この暗黙的な満足は強力です。なぜなら、型がすでに存在した後で interface を定義できるからです。外部パッケージの型に一致する interface を、そのソースコードを変更せずに作成できます。型はあなたの interface を知る必要がありません。正しく動作すればよいのです。
Goのダックタイピングは、コンパイル時の型安全性を維持しながら、動的言語の柔軟性を提供します。コンパイラは、型をinterface値として使用できるようにする前に、その型が実際に必要なメソッドを持っていることを検証します。
チャレンジ
簡単duck typing の動作を示すメッセージングシステムを構築しましょう。共通の interface を通じて、すべての型がメッセージを送信できるようにします。ただし、どの型も自分がそれを実装していることを明示的に宣言する必要はありません。
コードを3つのファイルに分けて整理します。
messengers.go: 同じ振る舞いをたまたま共有する、互いに無関係な3つの struct を Create します。Phone:Numberfield を持ちます。そのSendMessage(text string) stringmethod はSMS to [Number]: [text]を返します。Computer:Emailfield を持ちます。そのSendMessage(text string) stringmethod はEmail to [Email]: [text]を返します。Pigeon:Namefield を持ちます。そのSendMessage(text string) stringmethod は[Name] carries: [text]を返します。
sender.go:Messengerinterface を Define します。この interface はSendMessage(text string) stringmethod を要求します。Broadcastという名前の function を Create し、Messengerの slice と message string を受け取り、それぞれの messenger に対してSendMessageを calling した結果を含む string の slice を返すようにします。main.go: input から詳細を読み取り、各 messenger 型を one つずつ Create し、それらをMessengerの slice に Collect して、Broadcastを使ってすべての messenger 経由で message を送信します。それぞれの結果を独自の line に出力します。
次の input が提供されます。
- Line 1: Phone number
- Line 2: Email address
- Line 3: Pigeon name
- Line 4: Message to broadcast
たとえば、555-1234、alice@mail.com、Winston、Hello World が与えられた場合、出力は次のようになります。
SMS to 555-1234: Hello World
Email to alice@mail.com: Hello World
Winston carries: Hello Worldここでの重要な洞察は、Phone、Computer、Pigeon には共通点がまったくないということです。これらは base type を共有しておらず、Messenger を明示的に implement してもいません。それでも、すべてが同じように "quack" する(matching method を持つ)ため、Go ではそれらすべてを Messenger values として使用できます。これが実際の duck typing です。
自分で試してみよう
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// 電話番号を読み取る
scanner.Scan()
phoneNumber := scanner.Text()
// Read email address
scanner.Scan()
email := scanner.Text()
// ハトの名前を読み取る
scanner.Scan()
pigeonName := scanner.Text()
// ブロードキャストするメッセージを読み取る
scanner.Scan()
message := scanner.Text()
// TODO: 各メッセンジャータイプ(Phone、Computer、Pigeon)を1つずつ作成する
// TODO: すべてのメッセンジャーをMessengerのスライスに集める
// TODO: Broadcastを使ってすべてのメッセンジャー経由でメッセージを送信する
// TODO: 各結果をそれぞれの行に出力する
// 変数を使用するためのプレースホルダー(実装時に削除)
_ = phoneNumber
_ = email
_ = pigeonName
_ = message
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー自分で練習してみよう: Goオンラインコンパイラ