Menu
Coddy logo textTech

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!" }

DuckRobot も、それぞれの定義のどこにも Quacker について言及していません。それでも、正しいシグネチャを持つ Quack() メソッドがあるというだけで、どちらもインターフェースを満たします。

func MakeItQuack(q Quacker) {
    fmt.Println(q.Quack())
}

func main() {
    MakeItQuack(Duck{})   // Quack!
    MakeItQuack(Robot{})  // Beep-quack!
}

この暗黙的な満足は強力です。なぜなら、型がすでに存在した後で interface を定義できるからです。外部パッケージの型に一致する interface を、そのソースコードを変更せずに作成できます。型はあなたの interface を知る必要がありません。正しく動作すればよいのです。

Goのダックタイピングは、コンパイル時の型安全性を維持しながら、動的言語の柔軟性を提供します。コンパイラは、型をinterface値として使用できるようにする前に、その型が実際に必要なメソッドを持っていることを検証します。

challenge icon

チャレンジ

簡単

duck typing の動作を示すメッセージングシステムを構築しましょう。共通の interface を通じて、すべての型がメッセージを送信できるようにします。ただし、どの型も自分がそれを実装していることを明示的に宣言する必要はありません。

コードを3つのファイルに分けて整理します。

  • messengers.go: 同じ振る舞いをたまたま共有する、互いに無関係な3つの struct を Create します。
    • PhoneNumber field を持ちます。その SendMessage(text string) string method は SMS to [Number]: [text] を返します。
    • ComputerEmail field を持ちます。その SendMessage(text string) string method は Email to [Email]: [text] を返します。
    • PigeonName field を持ちます。その SendMessage(text string) string method は [Name] carries: [text] を返します。
    これらの型はいずれも interface について何も言及していないことに注目してください。同じ method signature をたまたま持っているだけです。
  • sender.go: Messenger interface を Define します。この interface は SendMessage(text string) string method を要求します。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-1234alice@mail.comWinstonHello World が与えられた場合、出力は次のようになります。

SMS to 555-1234: Hello World
Email to alice@mail.com: Hello World
Winston carries: Hello World

ここでの重要な洞察は、PhoneComputerPigeon には共通点がまったくないということです。これらは 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
}
quiz icon腕試し

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

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

自分で練習してみよう: Goオンラインコンパイラ