Menu
Coddy logo textTech

Goにおけるダックタイピング

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

ダックタイピング(duck typing)という用語は、「もしそれがアヒルのように歩き、アヒルのように鳴くなら、それはアヒルである」という格言に由来します。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{})   // クワッ!
    MakeItQuack(Robot{})  // ビープ・クワッ!
}

この暗黙的な充足は、型が既に存在した後にインターフェースを定義できるため、非常に強力です。外部パッケージの型に一致するインターフェースを、そのソースコードを修正することなく作成できます。型はあなたのインターフェースについて知る必要はなく、ただ正しく振る舞うだけでよいのです。

Goにおけるダックタイピングは、コンパイル時の型安全性を維持しながら、動的言語の柔軟性を提供します。コンパイラは、型がインターフェース値として使用される前に、実際に必要なメソッドを実装しているかどうかを検証します。

challenge icon

チャレンジ

簡単

ダックタイピングの動作を実証するメッセージングシステムを構築しましょう。共通のインターフェースを明示的に実装することを宣言せずに、共通のインターフェースを通じてメッセージを送信できる、完全に無関係な型を作成します。

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

  • messengers.go: たまたま同じ振る舞いを共有する、3つの無関係な構造体を作成します:
    • Number フィールドを持つ Phone — その SendMessage(text string) string メソッドは "SMS to [Number]: [text]" を返します。
    • Email フィールドを持つ Computer — その SendMessage(text string) string メソッドは "Email to [Email]: [text]" を返します。
    • Name フィールドを持つ Pigeon — その SendMessage(text string) string メソッドは "[Name] carries: [text]" を返します。
    これらの型はいずれもインターフェースについて言及していないことに注目してください。ただ、たまたま同じメソッドシグネチャを持っているだけです。
  • sender.go: SendMessage(text string) string メソッドを必要とする Messenger インターフェースを定義します。Messenger のスライスとメッセージ文字列を受け取り、各メッセンジャーで SendMessage を呼び出した結果を含む文字列のスライスを返す Broadcast という関数を作成します。
  • main.go: 入力から詳細を読み取り、各メッセンジャー型のインスタンスを1つずつ作成し、それらを Messenger のスライスにまとめ、Broadcast を使用してすべてのメッセンジャーを通じてメッセージを送信します。各結果をそれぞれの行に出力します。

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

  • 1行目: 電話番号
  • 2行目: メールアドレス
  • 3行目: 伝書鳩の名前
  • 4行目: 一斉送信するメッセージ

例えば、"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

ここでの重要な洞察は、PhoneComputerPigeon には共通点がないということです。これらは基本型を共有しておらず、Messenger を明示的に実装しているわけでもありません。しかし、それらがすべて同じように「鳴く」(一致するメソッドを持っている)ため、Goはそれらすべてを Messenger 値として使用することを許可します。これがダックタイピングの仕組みです。

チートシート

Goにおいて、ダックタイピングとは、型がインターフェースを実装していることを明示的に宣言する必要がないことを意味します。単に、一致するシグネチャを持つ必要なメソッドを持っているだけで十分です。

インターフェースが必要とするすべてのメソッドを型が持っていれば、その型は暗黙的にインターフェースを満たします:

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インターフェースを満たしています:

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

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

この暗黙的な充足により、型が既に存在した後にインターフェースを定義したり、外部パッケージの型に一致するインターフェースをソースコードを修正することなく作成したりすることが可能になります。コンパイラは、型が必要なメソッドを持っていることをコンパイル時に検証し、柔軟性を維持しながら型安全性を提供します。

自分で試してみよう

package main

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

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// 電話番号を読み込む
	scanner.Scan()
	phoneNumber := scanner.Text()
	
	// メールアドレスを読み込む
	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腕試し

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

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