Menu
Coddy logo textTech

インターフェースによるポリモーフィズム

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

ポリモーフィズムは、共通のインターフェースを通じて、異なる型を統一的に扱うことを可能にします。Goでは、継承やクラス階層を使用せず、完全にインターフェースのみによってこれが実現されます。

関数がインターフェース型を引数として受け取る場合、そのインターフェースを実装する任意の具象型を渡すことができます。関数は具体的な型を知る必要はなく、インターフェースによって定義された振る舞いのみを必要とします:

type Speaker interface {
    Speak() string
}

type Dog struct{ Name string }
func (d Dog) Speak() string { return "Woof!" }

type Cat struct{ Name string }
func (c Cat) Speak() string { return "Meow!" }

func MakeSound(s Speaker) {
    fmt.Println(s.Speak())
}

これで、MakeSoundSpeak() メソッドを持つ任意の型で動作するようになります:

func main() {
    dog := Dog{Name: "Rex"}
    cat := Cat{Name: "Whiskers"}
    
    MakeSound(dog)  // ワンワン!
    MakeSound(cat)  // ニャー!
}

同じ関数呼び出しでも、渡される実際の型に応じて異なる動作をします。これがポリモーフィズムの仕組みです。MakeSound 関数は一度記述するだけで、Speaker インターフェースを満たしている限り、無制限の数の型で動作します。

このアプローチにより、コードの柔軟性と拡張性が維持されます。「話す」機能を持つ新しい型を追加する場合でも、既存の関数を変更する必要はありません。インターフェースを実装するだけで、自動的に動作します。

challenge icon

チャレンジ

簡単

ポリモーフィズムの動作を実証する車両記述システムを構築しましょう。インターフェースを通じて共通の動作を共有する異なる車両タイプを作成し、どの車両でも動作する単一の関数を記述します。

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

  • vehicles.go: Describe() string メソッドを必要とする Describer インターフェースを定義します。次に、このインターフェースを独自の方法で実装する3つの車両タイプを作成します:
    • BrandModel フィールドを持つ Car — その Describe()"Car: [Brand] [Model]" を返します。
    • BrandEngineCC (int) フィールドを持つ Motorcycle — その Describe()"Motorcycle: [Brand] [EngineCC]cc" を返します。
    • Type フィールド("Mountain" や "Road" など)を持つ Bicycle — その Describe()"Bicycle: [Type]" を返します。
  • main.go: 任意の Describer を受け取り、Describe() を呼び出した結果を出力する PrintDescription という関数を作成します。入力から車両の詳細を読み取り、各車両タイプのインスタンスを1つずつ作成し、それぞれを PrintDescription に渡して、同じ関数が3つの異なるタイプすべてで動作することを確認します。

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

  • 1行目: 車のブランド (Car brand)
  • 2行目: 車のモデル (Car model)
  • 3行目: オートバイのブランド (Motorcycle brand)
  • 4行目: オートバイのエンジン排気量 (Motorcycle engine CC、整数)
  • 5行目: 自転車のタイプ (Bicycle type)

例えば、ToyotaCamryHonda600Mountain が与えられた場合、出力は以下のようになります:

Car: Toyota Camry
Motorcycle: Honda 600cc
Bicycle: Mountain

PrintDescription が、受け取っているのが Car なのか、Motorcycle なのか、Bicycle なのかを知る必要がないことに注目してください。単に Describe() を呼び出すだけで、各タイプが独自の出力を返します。これがポリモーフィズムです。1つの関数で、複数の振る舞いを実現します。

チートシート

ポリモーフィズム(多態性)により、共通のインターフェースを通じて異なる型を統一的に扱うことができます。Goでは、継承やクラス階層を使用せずに、インターフェースを通じてこれを実現します。

関数がパラメータとしてインターフェース型を受け取る場合、そのインターフェースを実装している任意の具象型を渡すことができます:

type Speaker interface {
    Speak() string
}

type Dog struct{ Name string }
func (d Dog) Speak() string { return "Woof!" }

type Cat struct{ Name string }
func (c Cat) Speak() string { return "Meow!" }

func MakeSound(s Speaker) {
    fmt.Println(s.Speak())
}

同じ関数が異なる型で動作します:

func main() {
    dog := Dog{Name: "Rex"}
    cat := Cat{Name: "Whiskers"}
    
    MakeSound(dog)  // Woof!
    MakeSound(cat)  // Meow!
}

関数は一度記述すれば、インターフェースを満たしている限り、無制限の型で動作します。新しい型を追加しても、既存の関数を変更する必要はありません。ただインターフェースを実装するだけです。

自分で試してみよう

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

// TODO: 任意の Describer を受け取る PrintDescription という名前の関数を作成します
// そして Describe() を呼び出した結果を出力します

func main() {
	reader := bufio.NewReader(os.Stdin)

	// 車の詳細を読み込む
	carBrand, _ := reader.ReadString('\n')
	carBrand = strings.TrimSpace(carBrand)
	carModel, _ := reader.ReadString('\n')
	carModel = strings.TrimSpace(carModel)

	// オートバイの詳細を読み込む
	motoBrand, _ := reader.ReadString('\n')
	motoBrand = strings.TrimSpace(motoBrand)
	motoEngineStr, _ := reader.ReadString('\n')
	motoEngineStr = strings.TrimSpace(motoEngineStr)
	motoEngine, _ := strconv.Atoi(motoEngineStr)

	// 自転車の詳細を読み込む
	bicycleType, _ := reader.ReadString('\n')
	bicycleType = strings.TrimSpace(bicycleType)

	// TODO: 入力値を使用して Car、Motorcycle、Bicycle を作成します

	// TODO: ポリモーフィズムを実演するために、各車両に対して PrintDescription を呼び出します
	fmt.Println("TODO: Print vehicle descriptions")
}
quiz icon腕試し

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

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