Menu
Coddy logo textTech

Goに継承がない理由

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

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

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

深い継承階層は、理解や保守が難しくなります。「脆弱な base クラス問題」は、base クラスへの変更によって派生クラスが予期せず壊れるときに発生します。

Goの設計者たちは、別の道を選びました。継承よりもコンポジションです。「Dog is an Animal」と言う代わりに、Goでは「Dog has Animalのような振る舞いを持つ」と表現することが推奨されます。この微妙な変化によって、より柔軟で保守しやすいコードにつながります。

Go は、すでに学んだ次の 2 つの仕組みを通じてコードの再利用を実現します。

  • インターフェースは実装の詳細を含めずに動作の契約を定義します
  • 構造体の埋め込みにより、型に他の型を含め、そのメソッドを再利用できます

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

この章では構造体の埋め込みについて詳しく説明し、Goが継承の欠点なしにその利点を実現する方法を示します。

challenge icon

チャレンジ

簡単

継承ではなく、Go のコンポジションアプローチを示す通知システムを構築しましょう。クラス階層ではなく、interface と struct の埋め込みを通じて動作を共有する型を作成します。

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

  • notifier.go: 単一の method Notify(message string) string を持つ Notifier interface を Define します。また、他の型に埋め込まれる Name field を持つ BaseNotifier struct も作成します。BaseNotifier に、角括弧内に notifier の名前を付けた message を返す Format(message string) string という method を追加します。
  • channels.go: BaseNotifier を埋め込み、Notifier interface を Implement する 2 つの notification channel 型を作成します。
    • 追加の Address field を持つ EmailNotifier
    • 追加の Phone field を持つ SMSNotifier
    各型の Notify method は、埋め込まれた Format method を使用し、出力にそれぞれ固有の channel 情報を含める必要があります。
  • main.go: 任意の Notifier と message を accepts し、Notify を calling した結果を返す SendAlert という function を Create します。入力から notification の詳細を読み取り、両方の型の notifier を作成し、interface を通じてそれらを互換的に使用できることを示します。

次の入力が提供されます。

  • 1 行目: email 用の Notifier 名
  • 2 行目: Email address
  • 3 行目: SMS 用の Notifier 名
  • 4 行目: Phone number
  • 5 行目: Alert message

BaseNotifierFormat method は次を返す必要があります。

[Name] message

Notify methods は次を返す必要があります。

  • 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

両方の notifier 型が、埋め込みを通じて BaseNotifierFormat method を再利用し、それぞれ独自の Notify 実装を提供していることに注目してください。SendAlert function は、具体的な型を知らなくても任意の Notifier と動作します。これは、継承ではなくコンポジションを実際に活用している例です。

自分で試してみよう

package main

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

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

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// メール notifier の名前を読み取る
	scanner.Scan()
	emailName := scanner.Text()
	
	// メールアドレスを読み取る
	scanner.Scan()
	emailAddress := scanner.Text()
	
	// SMS notifier の名前を読み取る
	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 を使って両方の notifier 経由で alertMessage を送信する
	// そして結果を出力する
	
	// 未使用変数の警告を抑制する(変数を使用したらこれらを削除する)
	_ = emailName
	_ = emailAddress
	_ = smsName
	_ = phoneNumber
	_ = alertMessage
}
quiz icon腕試し

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

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

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