Menu
Coddy logo textTech

メソッドの昇格

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

構造体の埋め込みがフィールドを外部の構造体にプロモート(昇格)させるのと同様に、Goは埋め込まれた型からのメソッドもプロモートします。これは、埋め込まれた構造体で定義されたメソッドを、外部の構造体から直接呼び出せることを意味します。

type Engine struct {
    Horsepower int
}

func (e Engine) Start() string {
    return "Engine started"
}

type Car struct {
    Model string
    Engine  // 埋め込み
}

CarEngine を埋め込んでいるため、Start() メソッドは自動的に昇格されます:

func main() {
    c := Car{
        Model:  "Sedan",
        Engine: Engine{Horsepower: 200},
    }
    
    fmt.Println(c.Start())         // エンジンが始動しました
    fmt.Println(c.Engine.Start())  // こちらも動作します
}

この昇格(promotion)は、インターフェースにおいて重要な意味を持ちます。埋め込まれた型がインターフェースを満たしている場合、外側の型も自動的にそのインターフェースを満たすことになります。

type Starter interface {
    Start() string
}

func Ignite(s Starter) {
    fmt.Println(s.Start())
}

func main() {
    c := Car{Model: "Sedan", Engine: Engine{Horsepower: 200}}
    Ignite(c)  // CarはEngineを通じてStarterを満たします
}

Car 型は Starter を明示的に実装することはありませんが、埋め込まれた Engine が必要なメソッドを持っているため、インターフェースを満たしています。これが、Goが伝統的な継承を使わずに振る舞いの再利用を実現する方法です。

challenge icon

チャレンジ

簡単

埋め込み型のメソッドが外部型にどのようにプロモート(昇格)されるか、そしてこれがコンポジションを通じてどのようにインターフェースの充足を可能にするかを示す音楽プレイヤーシステムを構築しましょう。

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

  • audio.go: 音楽プレイヤーの構成要素を作成します:
    • Brand フィールド(string)を持ち、[Brand] playing audio を返す Play() string メソッドを持つ AudioPlayer 構造体
    • Play() string メソッドを要求する Playable インターフェース
    • AudioPlayer を埋め込んだ、Model フィールドを持つ Smartphone 構造体
    • AudioPlayer を埋め込んだ、Model フィールドを持つ Tablet 構造体
  • main.go: 任意の Playable を受け取り、Play() を呼び出した結果を返す StartPlayback という関数を作成します。入力からデバイス情報を読み取り、Smartphone と Tablet の両方を作成し、どちらの型も明示的に Play() を実装していないにもかかわらず、埋め込まれた AudioPlayer を通じて Playable インターフェースを満たしていることを示します。

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

  • 1行目: Smartphone のモデル名
  • 2行目: Smartphone のオーディオブランド
  • 3行目: Tablet のモデル名
  • 4行目: Tablet のオーディオブランド

各デバイスについて、そのモデル名を出力し、続いてそのデバイスを StartPlayback に渡した結果を出力してください。

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

iPhone 15
Apple Audio playing audio
iPad Pro
Beats playing audio

AudioPlayerPlay() メソッドが自動的に各外部型にプロモートされるため、追加のコードなしで Playable インターフェースを満たし、SmartphoneTablet の両方を StartPlayback に渡すことができる点に注目してください。

チートシート

構造体が別の型を埋め込むと、埋め込まれた型のメソッドは自動的に外側の構造体に昇格されます:

type Engine struct {
    Horsepower int
}

func (e Engine) Start() string {
    return "Engine started"
}

type Car struct {
    Model string
    Engine  // 埋め込み
}

func main() {
    c := Car{
        Model:  "Sedan",
        Engine: Engine{Horsepower: 200},
    }
    
    fmt.Println(c.Start())         // Engine started
    fmt.Println(c.Engine.Start())  // こちらも動作します
}

埋め込まれた型がインターフェースを満たしている場合、外側の型も自動的にそのインターフェースを満たします:

type Starter interface {
    Start() string
}

func Ignite(s Starter) {
    fmt.Println(s.Start())
}

func main() {
    c := Car{Model: "Sedan", Engine: Engine{Horsepower: 200}}
    Ignite(c)  // CarはEngineを通じてStarterを満たします
}

これにより、従来の継承を使用せずに、コンポジションを通じて振る舞いの再利用が可能になります。

自分で試してみよう

package main

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

// TODO: 任意の Playable を受け取り、
// Play() を呼び出した結果を返す StartPlayback 関数を作成してください

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// スマートフォンのモデルを読み込む
	scanner.Scan()
	smartphoneModel := scanner.Text()
	
	// スマートフォンのオーディオブランドを読み込む
	scanner.Scan()
	smartphoneBrand := scanner.Text()
	
	// タブレットのモデルを読み込む
	scanner.Scan()
	tabletModel := scanner.Text()
	
	// タブレットのオーディオブランドを読み込む
	scanner.Scan()
	tabletBrand := scanner.Text()
	
	// TODO: AudioPlayer を埋め込んだ Smartphone を作成してください
	
	// TODO: AudioPlayer を埋め込んだ Tablet を作成してください
	
	// TODO: スマートフォンのモデルを出力し、その後スマートフォンを引数にして StartPlayback を呼び出してください
	
	// TODO: タブレットのモデルを出力し、その後タブレットを引数にして StartPlayback を呼び出してください
	
	_ = smartphoneModel
	_ = smartphoneBrand
	_ = tabletModel
	_ = tabletBrand
	fmt.Println("TODO: Complete the implementation")
}
quiz icon腕試し

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

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