Menu
Coddy logo textTech

埋め込み vs 集約

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

埋め込みによって、フィールドやメソッドが外側の構造体に昇格されることを学びました。しかし、Goは集約(aggregation)もサポートしています。これは、別の構造体を名前付きフィールドとして含める手法です。それぞれの方法をいつ使用すべきかを理解することは、優れた設計の鍵となります。

埋め込みを使用すると、フィールド名を省略でき、内部型のメンバに直接アクセスできるようになります。

type Writer struct{}
func (w Writer) Write() string { return "writing" }

type Document struct {
    Writer  // 埋め込み - フィールド名なし
}

d := Document{}
d.Write()  // 直接アクセス

集約では、フィールドに明示的な名前を付けます。内部の型にアクセスするには、その名前を使用する必要があります:

type Document struct {
    writer Writer  // 集約 - 名前付きフィールド
}

d := Document{writer: Writer{}}
d.writer.Write()  // フィールド名を使用する必要がある

主な違いは、表現しようとしている関係性にあります。埋め込み(Embedding)は、外部の型が内部の型のアイデンティティを引き継ぐ「is-a」関係を示唆します。集約(Aggregation)は、外部の型が単に内部の型を保持するだけの「has-a」関係を示唆します。

外側の型に内側の型が実装しているインターフェースを満たさせたい場合や、直接的なメソッドアクセスによってAPIがよりクリーンになる場合は、埋め込みを使用します。内側の型が公開すべきではない実装の詳細である場合や、内側の型へのアクセス方法を明示的に制御したい場合は、集約を使用します。

challenge icon

チャレンジ

簡単

埋め込み(embedding)と集約(aggregation)をいつ使い分けるかを示すレポート生成システムを構築しましょう。一部のコンポーネントは直接アクセス可能(埋め込み)にし、他のコンポーネントは内部の実装詳細(集約)として保持するシステムを作成します。

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

  • formatter.go: Style フィールド(string)を持つ Formatter 構造体と、テキストをスタイルで囲んで [Style] text [/Style] のように返す Format(text string) string メソッドを作成します。これは直接公開すべきではない内部ヘルパーになります。
  • report.go: レポート生成を異なる方法で処理する2つの構造体を作成します:
    • AuthorDate フィールドを持つ Metadata 構造体 — これは直接アクセス可能であるべき公開情報を表します。
    • Title フィールドを持つ Report 構造体 — Metadata埋め込み(著者と日付に直接アクセスできるようにするため)、Formatter には集約formatter という名前付きフィールドを持つ内部実装詳細として)を使用します。
    内部のフォーマッタを使用してタイトルを整形し、メタデータを含める Generate() string メソッドを Report に追加します。
  • main.go: 入力からレポートの詳細を読み取り、埋め込まれたメタデータと集約されたフォーマッタの両方を持つ Report を作成します。レポートから直接著者にアクセスすること(埋め込みによる)と、内部で隠されたフォーマッタを使用する整形済みレポートを生成することの違いを示します。

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

  • 1行目: レポートのタイトル
  • 2行目: 著者名
  • 3行目: 日付
  • 4行目: フォーマッタのスタイル

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

[Style] text [/Style]

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

[formatted title]
Author: [Author], Date: [Date]

メインファイルでは、以下の3行を出力してください:

  1. Report から直接アクセスした著者(埋め込みによってフィールドが昇格することを示します)
  2. 埋め込まれた型名を通じてアクセスした日付(report.Metadata.Date
  3. Generate() を呼び出した結果

例えば、Quarterly ResultsJane Doe2024-01-15BOLD が与えられた場合、出力は以下のようになります:

Jane Doe
2024-01-15
[BOLD] Quarterly Results [/BOLD]
Author: Jane Doe, Date: 2024-01-15

埋め込みによって AuthorDate が Report から直接アクセスできる一方で、フォーマッタは隠されたままであることに注目してください。名前付きフィールドで集約されているため、report.Format() を直接呼び出すことはできません。これは、実践における「is-a」関係と「has-a」関係の違いを示しています。

チートシート

Goは、ある構造体を別の構造体に含めるための2つの方法をサポートしています:埋め込み(embedding)集約(aggregation)です。

埋め込みはフィールド名を省略し、内部型のフィールドとメソッドを外部構造体に昇格(promote)させます:

type Writer struct{}
func (w Writer) Write() string { return "writing" }

type Document struct {
    Writer  // 埋め込み - フィールド名なし
}

d := Document{}
d.Write()  // 埋め込まれたメソッドへの直接アクセス

集約は明示的なフィールド名を使用し、内部型にアクセスするためにその名前を必要とします:

type Document struct {
    writer Writer  // 集約 - 名前付きフィールド
}

d := Document{writer: Writer{}}
d.writer.Write()  // フィールド名を使用する必要がある

それぞれの使い分け:

  • 埋め込み:「is-a」関係を表します。外部型が内部型のインターフェースを満たす必要がある場合や、メソッドへの直接アクセスによってAPIが改善される場合に使用します。
  • 集約:「has-a」関係を表します。内部型が公開すべきではない実装の詳細である場合や、アクセスを明示的に制御したい場合に使用します。

埋め込みを使用しても、必要に応じて埋め込まれた型名を通じてフィールドにアクセスできます:

type Metadata struct {
    Author string
}

type Report struct {
    Metadata  // 埋め込み
}

r := Report{Metadata: Metadata{Author: "Jane"}}
r.Author              // 昇格による直接アクセス
r.Metadata.Author     // 型名による明示的なアクセス

自分で試してみよう

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
}
quiz icon腕試し

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

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