埋め込み vs 集約
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 38/107。
embedding によってフィールドとメソッドが外側の構造体に昇格することを学びました。しかし Go は、別の構造体を名前付きフィールドとして含める aggregation もサポートしています。それぞれのアプローチをいつ使用するかを理解することが、優れた設計の鍵です。
embeddingでは、フィールド名を省略し、内部型のメンバーに直接アクセスできるようになります。
type Writer struct{}
func (w Writer) Write() string { return "writing" }
type Document struct {
Writer // 埋め込み - フィールド名なし
}
d := Document{}
d.Write() // 直接アクセス
aggregationでは、field に explicit な名前を付け、その名前で内側の型に accessする必要があります。
type Document struct {
writer Writer // 集約 - 名前付きフィールド
}
d := Document{writer: Writer{}}
d.writer.Write() // フィールド名を使用する必要がある
主な違いは、表現している関係です。埋め込みは、外側の型が内側の型のアイデンティティを引き継ぐ「is-a」関係を示します。集約は、外側の型が内側の型を単に含む「has-a」関係を示します。
内側の型によって実装されたインターフェースを外側の型が満たすようにしたい場合や、directなメソッドアクセスによって API をより簡潔にしたい場合は、embeddingを使用します。内側の型が公開すべきでないimplementation detailである場合や、内側の型へのexplicitなアクセス方法を制御したい場合は、aggregationを使用します。
チャレンジ
簡単embedding と aggregation のどちらを使用すべきかを示すレポート生成システムを構築しましょう。一部のコンポーネントには直接 access できるようにし(embedding)、その他のコンポーネントは internal implementation detail として保持する(aggregation)システムを作成します。
コードを次の3つのファイルに整理します。
formatter.go:Stylefield(string)と、style でテキストを囲んだ結果([Style] text [/Style]のような形式)を返すFormat(text string) stringmethod を持つFormatterstruct を Create します。これは直接 expose すべきではない internal helper です。report.go: レポート生成を異なる方法で処理する2つの struct を Create します。AuthorとDateの fields を持つMetadatastruct。これは直接 access できるべき public information を表します。Titlefield を持つReportstruct。Metadataをembeds(Author と Date に直接 access するため)し、Formatterにはaggregationを使用します(named fieldformatterを持つ internal implementation detail として)。
ReportにGenerate() stringmethod を Add します。この method は internal formatter を使用して title を format し、metadata を含めます。main.go: input から report details を読み取り、embedded metadata と aggregated formatter の両方を持つReportを Create します。embedding によって report 上の Author に直接 access し、その後、hidden formatter を internal に使用して formatted report を Generate することで、その違いを示します。
次の input が提供されます。
- Line 1: Report title
- Line 2: Author name
- Line 3: Date
- Line 4: Formatter style
Format method は次を return する必要があります。
[Style] text [/Style]Generate method は次を return する必要があります。
[formatted title]
Author: [Author], Date: [Date]main file では、3行を print します。
- Report 上で直接 access した Author(embedding によって field が promote されることを demonstrates)
- embedded type name(
report.Metadata.Date)を通じて access した Date Generate()を calling した結果
たとえば、Quarterly Results、Jane Doe、2024-01-15、BOLD が与えられた場合、output は次のようになります。
Jane Doe
2024-01-15
[BOLD] Quarterly Results [/BOLD]
Author: Jane Doe, Date: 2024-01-15Author と Date は embedding によって Report 上で直接 accessible である一方、formatter は hidden のままであることに注目してください。formatter は named field として aggregated されているため、report.Format() を直接 calling することはできません。これは実際の "is-a"
と "has-a"
の relationship を demonstrates しています。
自分で試してみよう
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
title := scanner.Text()
scanner.Scan()
author := scanner.Text()
scanner.Scan()
date := scanner.Text()
scanner.Scan()
style := scanner.Text()
// TODO: author と date を持つ Metadata 構造体を作成する
// TODO: 指定された style で Formatter を作成する
// TODO: Metadata を埋め込み、Formatter を集約する Report を作成する
// TODO: Report 上で直接アクセスした author を出力する(埋め込みによりフィールドが昇格する)
// TODO: 埋め込み型名を通じてアクセスした date を出力する (report.Metadata.Date)
// TODO: Generate() を呼び出した結果を出力する
// 未使用変数の警告を抑制する(変数を使用したらこれらを削除する)
_ = title
_ = author
_ = date
_ = style
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー自分で練習してみよう: Goオンラインコンパイラ