チュートリアル

Go基礎:構造体

Go入門構造体
広告エリア

構造体の定義

// 構造体の定義
type Person struct {
    Name string
    Age  int
}

// インスタンス化
var p1 Person
p1.Name = "太郎"
p1.Age = 25

// リテラルで初期化
p2 := Person{
    Name: "花子",
    Age:  30,
}

// フィールド順で初期化(非推奨)
p3 := Person{"次郎", 20}

// ポインタで作成
p4 := &Person{
    Name: "三郎",
    Age:  35,
}

フィールドへのアクセス

type User struct {
    Name  string
    Email string
    Age   int
}

user := User{Name: "太郎", Email: "taro@example.com", Age: 25}

// フィールドへのアクセス
fmt.Println(user.Name)
user.Age = 26

// ポインタでも同じ構文
userPtr := &user
fmt.Println(userPtr.Name)  // (*userPtr).Name と同じ
userPtr.Age = 27

メソッド

値レシーバ

type Rectangle struct {
    Width  float64
    Height float64
}

// 値レシーバのメソッド
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

rect := Rectangle{Width: 10, Height: 5}
fmt.Println(rect.Area())       // 50
fmt.Println(rect.Perimeter())  // 30

ポインタレシーバ

type Counter struct {
    count int
}

// ポインタレシーバのメソッド(状態を変更)
func (c *Counter) Increment() {
    c.count++
}

func (c *Counter) Value() int {
    return c.count
}

counter := Counter{}
counter.Increment()
counter.Increment()
fmt.Println(counter.Value())  // 2

値レシーバ vs ポインタレシーバ

// 値レシーバ
// - 状態を変更しない
// - 小さな構造体

// ポインタレシーバ
// - 状態を変更する
// - 大きな構造体(コピーを避ける)
// - 一貫性のため(1つでもポインタなら全てポインタ)

type User struct {
    Name string
    Age  int
}

// 値レシーバ(読み取りのみ)
func (u User) String() string {
    return fmt.Sprintf("%s (%d歳)", u.Name, u.Age)
}

// ポインタレシーバ(変更あり)
func (u *User) Birthday() {
    u.Age++
}

コンポジション

Goに継承はありませんが、埋め込みで同様の効果を得られます。

type Animal struct {
    Name string
}

func (a Animal) Speak() {
    fmt.Printf("%s が鳴いています\n", a.Name)
}

type Dog struct {
    Animal  // 埋め込み
    Breed string
}

func (d Dog) Fetch() {
    fmt.Printf("%s がボールを取ってきます\n", d.Name)
}

// 使用
dog := Dog{
    Animal: Animal{Name: "ポチ"},
    Breed:  "柴犬",
}

dog.Speak()  // "ポチ が鳴いています"
dog.Fetch()  // "ポチ がボールを取ってきます"

// 埋め込まれたフィールドに直接アクセス
fmt.Println(dog.Name)  // "ポチ"

オーバーライド

type Cat struct {
    Animal
}

// メソッドのオーバーライド
func (c Cat) Speak() {
    fmt.Printf("%s がニャーと鳴いています\n", c.Name)
}

cat := Cat{Animal: Animal{Name: "タマ"}}
cat.Speak()  // "タマ がニャーと鳴いています"

// 親のメソッドを呼び出す
cat.Animal.Speak()  // "タマ が鳴いています"

タグ

フィールドにメタデータを付与します。

type User struct {
    ID       int    `json:"id"`
    Name     string `json:"name"`
    Email    string `json:"email,omitempty"`
    Password string `json:"-"`  // JSONに含めない
}

// JSONシリアライズ
user := User{ID: 1, Name: "太郎", Email: "", Password: "secret"}
data, _ := json.Marshal(user)
fmt.Println(string(data))
// {"id":1,"name":"太郎"}

リフレクションでタグを取得

import "reflect"

type Config struct {
    Host string `env:"HOST" default:"localhost"`
    Port int    `env:"PORT" default:"8080"`
}

t := reflect.TypeOf(Config{})
field, _ := t.FieldByName("Host")
fmt.Println(field.Tag.Get("env"))      // "HOST"
fmt.Println(field.Tag.Get("default"))  // "localhost"

コンストラクタパターン

type User struct {
    name  string
    email string
    age   int
}

// コンストラクタ関数
func NewUser(name, email string, age int) *User {
    return &User{
        name:  name,
        email: email,
        age:   age,
    }
}

// バリデーション付き
func NewUserWithValidation(name, email string, age int) (*User, error) {
    if name == "" {
        return nil, errors.New("名前は必須です")
    }
    if age < 0 {
        return nil, errors.New("年齢は0以上である必要があります")
    }
    return &User{name: name, email: email, age: age}, nil
}

実践例:銀行口座

package main

import (
    "errors"
    "fmt"
)

type BankAccount struct {
    accountNumber string
    ownerName     string
    balance       float64
}

func NewBankAccount(owner string, initial float64) *BankAccount {
    return &BankAccount{
        accountNumber: generateAccountNumber(),
        ownerName:     owner,
        balance:       initial,
    }
}

func (b *BankAccount) Deposit(amount float64) error {
    if amount <= 0 {
        return errors.New("金額は正の値である必要があります")
    }
    b.balance += amount
    return nil
}

func (b *BankAccount) Withdraw(amount float64) error {
    if amount <= 0 {
        return errors.New("金額は正の値である必要があります")
    }
    if amount > b.balance {
        return errors.New("残高不足です")
    }
    b.balance -= amount
    return nil
}

func (b *BankAccount) Balance() float64 {
    return b.balance
}

func (b *BankAccount) String() string {
    return fmt.Sprintf("口座: %s, 名義: %s, 残高: %.2f円",
        b.accountNumber, b.ownerName, b.balance)
}

var accountSeq = 1000

func generateAccountNumber() string {
    accountSeq++
    return fmt.Sprintf("ACC-%d", accountSeq)
}

func main() {
    account := NewBankAccount("山田太郎", 10000)
    fmt.Println(account)

    account.Deposit(5000)
    fmt.Printf("入金後: %.2f\n", account.Balance())

    if err := account.Withdraw(3000); err != nil {
        fmt.Println("エラー:", err)
    }
    fmt.Printf("出金後: %.2f\n", account.Balance())
}

まとめ

  • type Name struct { ... }で構造体を定義
  • フィールドは大文字で公開、小文字で非公開
  • func (r Type) Method()でメソッドを定義
  • ポインタレシーバで状態を変更
  • 埋め込みでコンポジション
  • タグでメタデータを付与
  • NewXxxパターンでコンストラクタ

次回はインターフェースについて学びます。

広告エリア