0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

実用Go言語 第2章「定義型」まとめ

Posted at

この章では、Go言語における「型を定義する力」について解説されています。
単なる構造体定義にとどまらず、組み込み型の拡張やアプリケーションのドメイン設計にも型を活用できることがわかります。

2.1 型を定義してデータの不整合を防止する

2.1.1 Goにおける型の定義

  • typeキーワードで新しい型を定義可能
  • 目的に応じた意味づけされた型を作成することで、不正な代入を防ぐ
type UserID string
type Price int

2.2 既存のデータ型を拡張する

2.2.1 メソッドを追加して組み込み型を拡張する

  • type MyString string のように再定義し、独自のメソッドを生やす
type MyString string

func (m MyString) Upper() string {
    return strings.ToUpper(string(m))
}

2.2.2 拡張した型でも元の機能は利用できる

標準型を元に新しい型を定義すると、元の機能を活かしつつメソッドを追加できます。

例:url.Values を拡張

type MyValues url.Values

func (v MyValues) GetOrDefault(key, def string) string {
    if val := url.Values(v).Get(key); val != "" {
        return val
    }
    return def
}

利用例:

m := MyValues{"lang": {"ja"}}
fmt.Println(url.Values(m).Get("lang"))        // "ja"
fmt.Println(m.GetOrDefault("mode", "debug"))  // "debug"

2.3 アプリケーションドメインに対応した型定義

2.3.1 スライスへの型定義

スライスに型を定義すると、問い合わせ結果に対する処理をメソッドにまとめることができます。

例:UserList 型にドメインロジックを委譲

type User struct {
    Name string
    Age  int
}

type UserList []User

// 未成年ユーザーのみを抽出するドメインロジック
func (ul UserList) Underage() UserList {
    var result UserList
    for _, u := range ul {
        if u.Age < 20 {
            result = append(result, u)
        }
    }
    return result
}

利用例(DB取得結果に対して)

users := UserList{
    {Name: "Taro", Age: 17},
    {Name: "Jiro", Age: 25},
}

fmt.Println(users.Underage()) // [{Taro 17}]

このように、取得結果(スライス)に意味を持たせたメソッドを定義することで、可読性と保守性が高まります。
ロジックがサービス層に散らばらず、ドメイン単位でまとまるのが利点です。

2.3.2 値への型定義

stringint などの基本型に明示的な意味を持たせるために型を定義すると、可読性と型安全性が向上します。

例:SKUコードを明示的に扱う

type SKU string

func (s SKU) IsValid() bool {
    return strings.HasPrefix(string(s), "SKU-")
}

利用例:

var code SKU = "SKU-12345"
if code.IsValid() {
    fmt.Println("有効なSKUコードです")
}
  • string のままだと "SKU-12345" なのか "USER-ID" なのか区別がつきませんが、 SKU 型にすることで意味づけと専用メソッドを持たせられます。

2.3.3 列挙への型定義

  • iotaと組み合わせて列挙型を定義し、状態管理などに使う
type Status int

const (
    StatusUnknown Status = iota
    StatusActive
    StatusInactive
)

2.3.4 構造体への型定義

構造体に独自型を定義し、引数として渡すのではなくレシーバーメソッドで操作することで、意図が明確なコードになります。

例:注文情報を表す Order 構造体にロジックを持たせる

type OrderID string
type Yen int

type Order struct {
    ID     OrderID
    Amount Yen
}

// 割引処理などのドメインロジックをレシーバーで保持
func (o *Order) ApplyDiscount(rate float64) {
    o.Amount = Yen(float64(o.Amount) * rate)
}

利用例:

order := Order{ID: "ORD-001", Amount: 1000}
order.ApplyDiscount(0.9)
fmt.Println(order.Amount) // 900
  • Order を毎回関数に渡すのではなく、自分自身に責務を持たせることで自然な設計になります。

このように、構造体を「ただのデータの入れ物」にせず、意味ある振る舞いを持つ自己完結型にすることで、保守性と意図の明確さが向上します。

2.4 型の変換(キャスト)

2.4.1 型変換によって型をキャストする

Goでは、別名がついた型(定義型)は元が同じでも別の型として扱われます。
そのため、暗黙の代入は不可で、明示的な変換が必要です。

例:

type UserID string
type Email string

var id UserID = "user-123"
// var email Email = id // エラー!別の型なので代入できない

var email Email = Email(id) // 明示的にキャストすればOK
  • UserIDEmailstring ベースですが、Goでは完全に別物とみなされます。
  • 「同じベース型でも互換性なし」=明示的な型変換が必要です。

このルールのおかげで、意味の異なるデータをうっかり代入してしまうバグを防止できます。

2.5 機密情報を扱うフィールドを定義して出力書式をカスタマイズする

2.5.1 実装方法

  • String()メソッドを実装することでfmt.Println()等での出力を制御可能
type Password string

func (p Password) String() string {
    return "****" // 出力時には伏せ字に
}

✅ まとめ

Goでは型を「意味づけ」「安全性」「ドメイン設計」の武器として活用できます。

  • 組み込み型の拡張でメソッド追加
  • iotaや定義型で状態の明示化
  • ファクトリー関数で安全に初期化

型を活用することで、より堅牢で読みやすいGoコードを実現しましょう。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?