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

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

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

### 2.1.1 Goにおける型の定義
- `type`キーワードで新しい型を定義可能
- 目的に応じた**意味づけされた型**を作成することで、不正な代入を防ぐ

```go
type UserID string
type Price int
```

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

### 2.2.1 メソッドを追加して組み込み型を拡張する
- `type MyString string` のように再定義し、独自のメソッドを生やす

```go
type MyString string

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

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

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

#### 例：url.Values を拡張

```go
type MyValues url.Values

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

```go
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` 型にドメインロジックを委譲

```go
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取得結果に対して）

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

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

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


### 2.3.2 値への型定義

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

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

```go
type SKU string

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

#### 利用例：

```go
var code SKU = "SKU-12345"
if code.IsValid() {
    fmt.Println("有効なSKUコードです")
}
```

- `string` のままだと `"SKU-12345"` なのか `"USER-ID"` なのか区別がつきませんが、 `SKU` 型にすることで**意味づけと専用メソッド**を持たせられます。


### 2.3.3 列挙への型定義
- `iota`と組み合わせて列挙型を定義し、状態管理などに使う

```go
type Status int

const (
    StatusUnknown Status = iota
    StatusActive
    StatusInactive
)
```

### 2.3.4 構造体への型定義

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

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

```go
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)
}
```
#### 利用例：

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



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



## 2.4 型の変換（キャスト）

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

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

#### 例：

```go
type UserID string
type Email string

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

var email Email = Email(id) // 明示的にキャストすればOK
```

- `UserID` も `Email` も `string` ベースですが、Goでは**完全に別物**とみなされます。
- **「同じベース型でも互換性なし」＝明示的な型変換が必要**です。


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

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

### 2.5.1 実装方法
- `String()`メソッドを実装することで`fmt.Println()`等での出力を制御可能

```go
type Password string

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

## ✅ まとめ

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

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

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