構造体の定義
// 構造体の定義
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パターンでコンストラクタ
次回はインターフェースについて学びます。