Menu
Coddy logo textTech

Goに継承がない理由

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

JavaやC++のような伝統的なオブジェクト指向言語は、型の間でコードを共有するために継承を使用します。子クラスは親クラスを拡張し、そのすべてのフィールドとメソッドを継承します。Goは意図的にこの機能を省いています。

継承は型同士の間に密結合を生み出します。親クラスが変更されると、すべての小クラスが影響を受けます。

深い継承階層は、理解やメンテナンスが困難になります。「壊れやすい基底クラスの問題(fragile base class problem)」は、基底クラスへの変更が派生クラスを予期せず破損させたときに発生します。

Goの設計者は異なる道を選びました:継承よりもコンポジション。「犬は動物である」と言う代わりに、Goは「犬は動物のような振る舞いを持っている」と言うことを推奨しています。この微妙な転換が、より柔軟でメンテナンス性の高いコードにつながります。

Goは、すでに学んだ2つのメカニズムを通じてコードの再利用を実現します。

  • インターフェースは、実装の詳細を持たずに振る舞いの契約を定義します
  • 構造体の埋め込みにより、型が他の型を含み、そのメソッドを再利用できるようになります。

この比較を考えてみましょう。伝統的なOOPでは、class Dog extends Animalのように記述するかもしれません。Goでは、Animal構造体をDogの中に埋め込み、共通のインターフェースを実装します。その結果、型同士の結合がより緩やかになりながら、同様の機能が得られます。

この章では、構造体の埋め込み(struct embedding)について深く掘り下げ、Goが継承の欠点を伴わずにその利点をどのように実現しているかを示します。

challenge icon

チャレンジ

簡単

継承の代わりに Go のコンポジション(合成)アプローチを実証する通知システムを構築しましょう。クラス階層ではなく、インターフェースと構造体の埋め込み(struct embedding)を通じて動作を共有する型を作成します。

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

  • notifier.go: Notify(message string) string という単一のメソッドを持つ Notifier インターフェースを定義します。また、他の型に埋め込まれる Name フィールドを持つ BaseNotifier 構造体を作成します。BaseNotifier には、メッセージの前にブラケットで囲んだ通知者名を付加して返す Format(message string) string というメソッドを実装してください。
  • channels.go: BaseNotifier を埋め込み、Notifier インターフェースを実装する2つの通知チャネル型を作成します:
    • 追加の Address フィールドを持つ EmailNotifier
    • 追加の Phone フィールドを持つ SMSNotifier
    各型の Notify メソッドは、埋め込まれた Format メソッドを使用し、出力にそれぞれのチャネル固有の情報を含める必要があります。
  • main.go: 任意の Notifier とメッセージを受け取り、Notify を呼び出した結果を返す SendAlert という関数を作成します。入力から通知の詳細を読み取り、両方の型の通知者を作成し、インターフェースを通じてそれらを互換的に使用できることを示してください。

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

  • 1行目: メール通知者の名前
  • 2行目: メールアドレス
  • 3行目: SMS通知者の名前
  • 4行目: 電話番号
  • 5行目: アラートメッセージ

BaseNotifierFormat メソッドは以下を返す必要があります:

[Name] message

Notify メソッドは以下を返す必要があります:

  • EmailNotifier: Email to [Address]: [formatted message]
  • SMSNotifier: SMS to [Phone]: [formatted message]

例えば、Alertsuser@mail.comUrgent555-1234Server down が与えられた場合、出力は以下のようになります:

Email to user@mail.com: [Alerts] Server down
SMS to 555-1234: [Urgent] Server down

両方の通知者型が埋め込みを通じて BaseNotifier から Format メソッドを再利用しつつ、それぞれが独自の Notify 実装を提供していることに注目してください。SendAlert 関数は、具体的な型を知ることなく、任意の Notifier で動作します。これが「継承よりもコンポジション」を実践した形です。

チートシート

Goは、型の間でコードを共有するために継承よりもコンポジションを使用し、従来のオブジェクト指向の継承における密結合や壊れやすい基底クラスの問題を回避します。

クラス階層の代わりに、Goは以下を通じてコードの再利用を実現します:

  • インターフェース - 実装の詳細なしで振る舞いの契約を定義します
  • 構造体の埋め込み - 型が他の型を含み、そのメソッドを再利用できるようにします

「犬は動物である」(継承)と言うのではなく、Goは「犬は動物のような振る舞いを持っている」(コンポジション)という考え方を推奨します。

構造体の埋め込みの例

埋め込まれるベースとなる構造体を定義します:

type BaseNotifier struct {
    Name string
}

func (b BaseNotifier) Format(message string) string {
    return "[" + b.Name + "] " + message
}

フィールドとメソッドを再利用するために、ベースとなる構造体を他の型に埋め込みます:

type EmailNotifier struct {
    BaseNotifier  // 埋め込まれた構造体
    Address string
}

func (e EmailNotifier) Notify(message string) string {
    // 埋め込まれたFormatメソッドを直接呼び出すことができます
    return "Email to " + e.Address + ": " + e.Format(message)
}

複数の型が実装できるインターフェースを定義します:

type Notifier interface {
    Notify(message string) string
}

インターフェースを使用して、異なる具体的な型を互換的に操作します:

func SendAlert(n Notifier, message string) string {
    return n.Notify(message)
}

このアプローチは継承と同様の機能を提供しますが、型同士の結合はより緩やかになります。

自分で試してみよう

package main

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

// SendAlert は任意の Notifier とメッセージを受け取り、Notify を呼び出した結果を返します
// TODO: SendAlert 関数を実装してください

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// メールの通知者名を読み込む
	scanner.Scan()
	emailName := scanner.Text()
	
	// メールアドレスを読み込む
	scanner.Scan()
	emailAddress := scanner.Text()
	
	// SMS の通知者名を読み込む
	scanner.Scan()
	smsName := scanner.Text()
	
	// 電話番号を読み込む
	scanner.Scan()
	phoneNumber := scanner.Text()
	
	// アラートメッセージを読み込む
	scanner.Scan()
	alertMessage := scanner.Text()
	
	// TODO: emailName と emailAddress を使用して EmailNotifier を作成してください
	
	// TODO: smsName と phoneNumber を使用して SMSNotifier を作成してください
	
	// TODO: SendAlert を使用して両方の通知者から alertMessage を送信し、
	// その結果をプリントしてください
	
	// 未使用変数の警告を抑制(変数を使用する際にこれらを削除してください)
	_ = emailName
	_ = emailAddress
	_ = smsName
	_ = phoneNumber
	_ = alertMessage
}
quiz icon腕試し

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

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