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が継承の欠点なしにその利点を実現する方法を示します。
チャレンジ
簡単継承ではなく、Go のコンポジションアプローチを示す通知システムを構築しましょう。クラス階層ではなく、interface と struct の埋め込みを通じて動作を共有する型を作成します。
コードを 3 つのファイルに分けて整理します。
notifier.go: 単一の methodNotify(message string) stringを持つNotifierinterface を Define します。また、他の型に埋め込まれるNamefield を持つBaseNotifierstruct も作成します。BaseNotifierに、角括弧内に notifier の名前を付けた message を返すFormat(message string) stringという method を追加します。channels.go:BaseNotifierを埋め込み、Notifierinterface を Implement する 2 つの notification channel 型を作成します。- 追加の
Addressfield を持つEmailNotifier - 追加の
Phonefield を持つSMSNotifier
Notifymethod は、埋め込まれたFormatmethod を使用し、出力にそれぞれ固有の 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
BaseNotifier の Format method は次を返す必要があります。
[Name] messageNotify methods は次を返す必要があります。
- EmailNotifier:
Email to [Address]: [formatted message] - SMSNotifier:
SMS to [Phone]: [formatted message]
たとえば、Alerts、user@mail.com、Urgent、555-1234、Server down が与えられた場合、出力は次のようになります。
Email to user@mail.com: [Alerts] Server down
SMS to 555-1234: [Urgent] Server down両方の notifier 型が、埋め込みを通じて BaseNotifier の Format 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
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー自分で練習してみよう: Goオンラインコンパイラ