メインコンテンツまでスキップ

GOの構造体を理解しよう

Go言語では、構造体(struct)を使用して複数の関連するデータを一つのまとまりとして扱うことができます。 構造体は、オブジェクト指向プログラミングにおけるクラスに似た概念であり、フィールドとメソッドを持つ。 クラスとは異なり、Goの構造体は継承をサポートしていませんが、合成や埋め込みを使用して柔軟なデータ構造も作成できます。

構造体の宣言と初期化

※構造体のフィールド名は大文字/小文字に注意 大文字で始まるフィールド名はエクスポートされ、パッケージ外からアクセス可能になります。小文字で始まるフィールド名は非エクスポートされ、パッケージ内でのみアクセス可能です。

  • 先頭大文字→ 公開(他パッケージからアクセスOK)
  • 先頭小文字→ 非公開(パッケージ内だけ)
type User struct {
Name string
Age int
}

インスタンスは型(Type) から作られた、実体(具体的なデータ) のこと。 構造体のインスタンスを作成し、フィールドに値を設定する方法はいくつかあります。

直接作る

u := User{"Ken", 30}

フィールド名を指定して作る

u := User{Name: "Ken", Age: 30}

new関数を使って作る(ほぼ見ないけど)

u := new(User)
u.Name = "Ken"
u.Age = 30

構造体のフィールドへのアクセス

構造体のフィールドにはドット(.)を使ってアクセスします。

package main

import "fmt"

type Person struct {
Name string
Age int
}

func main() {
p := Person{Name: "テスト太郎", Age: 30}

fmt.Println(p.Name)
fmt.Println(p.Age)
}

構造体のメソッド

Goにはクラスがないけど、 struct にメソッド(関数)を“くっつける”ことはできる。

メソッドの定義とレシーバ

関数の前に (p Person) が付いたら、それはメソッド。 これはレシーバreceiverと呼ばれ、「この関数は Person 型に属しますよ」という宣言。

  • p … レシーバ
  • (p Person) … 値レシーバ
package main

import (
"fmt"
)

type Person struct {
Name string
Age int
}

// 構造体Personに対するメソッドSelfIntroductionを定義
func (p Person) SelfIntroduction() string {
return "私の名前は" + p.Name + "です。"
}

func main() {
p := Person{Name: "テスト太郎", Age: 30}
value := p.SelfIntroduction()
fmt.Println(value)
}

構造体の合成(Composition)

Go は継承なしに構造体を合成ごうせいすることができます。 これにより、複雑なデータ構造を簡単に作成できます。

package main

import (
"fmt"
)

type Person struct {
Name string
Age int
MainJob Job // Person構造体にJob構造体をフィールドとして持つ
}

type Job struct {
Company string
Type string
}

func main() {

person := Person{
Name: "テスト太郎",
Age: 30,
MainJob: Job{
Company: "テスト株式会社",
Type: "ITエンジニア",
},
}

fmt.Println(person.Name)
fmt.Println(person.Age)
// 明示的にフィールド経由
// person.MainJobでJob構造体にアクセス
fmt.Println(person.MainJob.Company)
fmt.Println(person.MainJob.Type)

}

構造体の埋め込み(Embedding)

Personをフィールド名なしで書くことで、Employee構造体がPerson構造体のフィールドとメソッドを継承したかのように振る舞う。

つまり、Employee.Name と直接アクセス可能(委譲いじょう:delegation)

package main

import (
"fmt"
)

type Person struct {
Name string
Age int
}

type Employee struct {
Person // フィールダーなしでPerson構造体を埋め込む
CompanyName string
Department string
}

func main() {

employee := Employee{
Person: Person{
Name: "テスト太郎",
Age: 30,
},
CompanyName: "テスト株式会社",
Department: "開発部",
}

// 直接employee.xxxでPersonのフィールドにアクセスできる
fmt.Println(employee.Name)
fmt.Println(employee.Age)
fmt.Println(employee.CompanyName)
fmt.Println(employee.Department)

}

合成と埋め込みの違い

  • 合成(Composition)は、構造体をフィールドとして持つことで、明示的にアクセスする必要があります。
  • 埋め込み(Embedding)は、構造体をフィールド名なしで持つことで、そのフィールドやメソッドに直接アクセスできます。

比喩ひゆ的に言うと:

言葉比喩
Composition家に“冷蔵庫がある”
Embedding家に“冷蔵庫は壁に埋め込みで見えないけど使える”
使うケース理由
普通の合成"has-a" 関係を明示したい
埋め込み共通メソッドを自然に使いたい
埋め込み小さなミックスイン風の設計(Logger、TimeStamp、Configなど)

合成の例: 商品が価格情報を持つ

Product構造体がPrice構造体をフィールドとして持つ

package main

import "fmt"

type Price struct {
Amount int
Unit string
}

type Product struct {
Name string
Price Price
}

func main() {
p := Product{
Name: "Apple",
Price: Price{
Amount: 150,
Unit: "JPY",
},
}

fmt.Println(p.Name)
fmt.Println(p.Price.Amount, p.Price.Unit)
}

埋め込みの例:Logger構造体の埋め込み

便利ミックスインみたいな使い方

type Logger struct{}
func (l Logger) Log(msg string) { fmt.Println(msg) }

type Service struct {
Logger
}

s := Service{}
s.Log("start") // s.Logger.Log("start")

まとめ

Go言語の構造体は、関連するデータをまとめて扱うための強力なツールです。 構造体の宣言、初期化、フィールドへのアクセス、メソッドの定義、合成、埋め込みなどの基本的な概念を理解することで、より複雑なデータ構造を効果的に扱うことができます。