1. 導入:なぜ継承ではなく「埋め込み」なのか
Go言語には、JavaやC++のようなクラスベースの「継承」という概念が存在しません。その代わりにGoが採用しているのが、型を構造体に含める「埋め込み(Embedding)」によるコンポジション(合成)です。継承は親子の関係を固定してしまいますが、埋め込みを利用することで、必要な機能を柔軟に組み合わせ、再利用性の高いコードを書くことができます。本記事では、このGo特有の強力な機能を解説します。
2. 基礎知識:埋め込みとメソッドのプロモーション
Goの構造体における埋め込みとは、他の型をフィールド名なしで構造体の中に定義することです。これにより、埋め込まれた型のフィールドやメソッドが、外側の構造体から直接アクセス可能になります。この仕組みを「メソッドのプロモーション(昇格)」と呼びます。コンパイラが自動的にメソッドを解決してくれるため、あたかもその構造体がメソッドを持っているかのように振る舞うことができます。
3. 実装/解決策:責務を分離して組み合わせる
実装のコツは、「一つの構造体には一つの責務を持たせる」ことです。例えば、「ログ出力機能」や「DB操作機能」といった小さな単位で構造体を作り、それらを必要な構造体に埋め込むことで、責務をフラットに保ちながら機能を拡張できます。これにより、テストが容易になり、コードの見通しも良くなります。
4. サンプルプログラム
以下のコードは、Logger機能を持つ構造体をService構造体に埋め込み、機能を利用する例です。
package main
import "fmt"
// Logger 構造体:ログ出力という単一の責務を持つ
type Logger struct{}
func (l Logger) Log(message string) {
fmt.Println("[LOG]:", message)
}
// Service 構造体:Loggerを埋め込むことで機能を合成する
type Service struct {
Logger // フィールド名を省略して型のみを記述(埋め込み)
}
func main() {
// Serviceを初期化
s := Service{}
// LoggerのメソッドであるLogが、Serviceのメソッドのように呼び出せる
// これがメソッドのプロモーションです
s.Log("サービスが開始されました")
}
5. 応用・注意点:過度な埋め込みへの警告
埋め込みは非常に便利ですが、注意点もあります。
メソッド名の衝突
異なる埋め込み型で同じ名前のメソッドが存在する場合、コンパイルエラー(あるいは曖昧な呼び出し)が発生します。この場合は、明示的にフィールドを指定して呼び出す必要があります。
過度な埋め込みの弊害
あまりに多くの型を埋め込みすぎると、どの構造体にどのメソッドが定義されているのかが追いにくくなり、コードの複雑性が増します。また、インターフェースを満たすためだけに深く埋め込みを行うと、疎結合のメリットが失われることがあります。
「合成」の原則に従い、必要な機能だけを適切に組み合わせる意識を持つことが、Goらしいクリーンな設計への第一歩です。ぜひ、現場のプロジェクトでも積極的に活用してみてください。

コメント