Goのポリモーフィズムを活用！複数の型を1つのスライスで扱う方法

## はじめに
Goで複数の型を1つのスライスで扱う方法を記載します。

## ソースコード
```shell:ディレクトリ構成
~/go/go_generics  (main)$ tree
.
├── go.mod
├── items
│   ├── number_item.go
│   ├── single_item.go
│   └── string_item.go
├── main.go
└── types
    └── item.go

3 directories, 6 files
```

```go:main.go
package main

import (
	"fmt"
	"go_generics/items"
	"go_generics/types"
)

func main() {
	// ItemResultsの作成
	var itemResults types.ItemResults

	// 各種アイテムの追加
	itemResults.Add(items.NewStringItem())
	itemResults.Add(items.NewNumberItem())
	itemResults.Add(items.NewSingleItem([]string{"Apple", "Banana", "Cherry"}))

	// 全アイテムに対して値を設定
	fmt.Println("--- Setting 'Hello' to all items ---")
	if errs := itemResults.SaveDraftAll("Hello"); len(errs) > 0 {
		for _, err := range errs {
			fmt.Println("Error:", err)
		}
	}

	fmt.Println("\n--- Current values ---")
	for i, value := range itemResults.GetValues() {
		fmt.Printf("Item[%d]: %s\n", i, value)
	}

	fmt.Println("\n--- Setting valid values ---")
	itemResults[0].SaveDraft("Hello")  // StringItem
	itemResults[1].SaveDraft("123.45") // NumberItem
	itemResults[2].SaveDraft("Apple")  // SingleItem

	fmt.Println("\n--- Final values ---")
	for i, value := range itemResults.GetValues() {
		fmt.Printf("Item[%d]: %s\n", i, value)
	}

	// SaveDraftのテスト
	fmt.Println("\n--- Testing SaveDraft ---")
	for i, item := range itemResults {
		if err := item.SaveDraft("test"); err != nil {
			fmt.Printf("Item[%d] SaveDraft Error: %v\n", i, err)
		} else {
			fmt.Printf("Item[%d] SaveDraft OK\n", i)
		}
	}

	// Saveのテスト
	fmt.Println("\n--- Testing Save ---")
	for i, item := range itemResults {
		if err := item.Save("test"); err != nil {
			fmt.Printf("Item[%d] Save Error: %v\n", i, err)
		} else {
			fmt.Printf("Item[%d] Save OK\n", i)
		}
	}
}
```

```go:types/item.go
package types

import (
	"fmt"
)

// ItemResult インターフェース
type ItemResult interface {
	Save(value string) error
	SaveDraft(value string) error
	GetValue() string
}

// ItemResults は複数のItemResultを管理するためのスライス型
type ItemResults []ItemResult

// Add は新しいItemResultを追加します
func (items *ItemResults) Add(item ItemResult) {
	*items = append(*items, item)
}

// SaveAll は全てのアイテムに対して値を保存します
func (items ItemResults) SaveAll(value string) []error {
	var errors []error
	for i, item := range items {
		if err := item.Save(value); err != nil {
			errors = append(errors, fmt.Errorf("item[%d]: %w", i, err))
		}
	}
	return errors
}

// SaveDraftAll は全てのアイテムに対してドラフトとして保存します
func (items ItemResults) SaveDraftAll(value string) []error {
	var errors []error
	for i, item := range items {
		if err := item.SaveDraft(value); err != nil {
			errors = append(errors, fmt.Errorf("item[%d]: %w", i, err))
		}
	}
	return errors
}

// GetValues は全てのアイテムの値を取得します
func (items ItemResults) GetValues() []string {
	values := make([]string, len(items))
	for i, item := range items {
		values[i] = item.GetValue()
	}
	return values
}
```

```go:items/number_item.go
package items

import (
	"errors"
	"fmt"
	"strconv"
)

type NumberItem struct {
	Value float64
}

func NewNumberItem() *NumberItem {
	return &NumberItem{}
}

// formatCheck は数値のフォーマットチェックを実施します
func (n *NumberItem) formatCheck() error {
	if n.Value < 0 {
		return errors.New("value must be a non-negative number")
	}
	return nil
}

// requiredCheck は必須チェックを実施します
func (n *NumberItem) requiredCheck() error {
	if n.Value == 0 {
		return errors.New("value is required")
	}
	return nil
}

func (n *NumberItem) SetValue(value string) error {
	num, err := strconv.ParseFloat(value, 64)
	if err != nil {
		return errors.New("invalid number format")
	}
	n.Value = num
	return n.formatCheck()
}

func (n *NumberItem) GetValue() string {
	return fmt.Sprintf("%f", n.Value)
}

// Save は全てのバリデーションを実施します
func (n *NumberItem) Save(value string) error {
	num, err := strconv.ParseFloat(value, 64)
	if err != nil {
		return errors.New("invalid number format")
	}
	n.Value = num
	if err := n.formatCheck(); err != nil {
		return err
	}
	return n.requiredCheck()
}

// SaveDraft はフォーマットチェックのみを実施します
func (n *NumberItem) SaveDraft(value string) error {
	num, err := strconv.ParseFloat(value, 64)
	if err != nil {
		return errors.New("invalid number format")
	}
	n.Value = num
	return n.formatCheck()
}

```

```go:items/single_item.go
package items

import (
	"errors"
	"fmt"
)

type SingleItem struct {
	Value      string
	Candidates []string
}

func NewSingleItem(candidates []string) *SingleItem {
	return &SingleItem{
		Candidates: candidates,
	}
}

// formatCheck は選択肢のフォーマットチェックを実施します
func (s *SingleItem) formatCheck() error {
	for _, candidate := range s.Candidates {
		if s.Value == candidate {
			return nil
		}
	}
	return fmt.Errorf("value must be one of %v", s.Candidates)
}

// requiredCheck は必須チェックを実施します
func (s *SingleItem) requiredCheck() error {
	if s.Value == "" {
		return errors.New("value is required")
	}
	return nil
}

func (s *SingleItem) GetValue() string {
	return s.Value
}

// Save は全てのバリデーションを実施します
func (s *SingleItem) Save(value string) error {
	s.Value = value
	if err := s.formatCheck(); err != nil {
		return err
	}
	return s.requiredCheck()
}

// SaveDraft はフォーマットチェックのみを実施します
func (s *SingleItem) SaveDraft(value string) error {
	s.Value = value
	return s.formatCheck()
}
```

```go:items/string_item.go
package items

import (
	"errors"
)

// StringItem 文字列アイテムの実装
type StringItem struct {
	Value string
}

func NewStringItem() *StringItem {
	return &StringItem{}
}

// formatCheck は文字列のフォーマットチェックを実施します
func (s *StringItem) formatCheck() error {
	return nil // 特に制約なし
}

// requiredCheck は必須チェックを実施します
func (s *StringItem) requiredCheck() error {
	if s.Value == "" {
		return errors.New("value is required")
	}
	return nil
}

func (s *StringItem) GetValue() string {
	return s.Value
}

// Save は全てのバリデーションを実施します
func (s *StringItem) Save(value string) error {
	s.Value = value
	if err := s.formatCheck(); err != nil {
		return err
	}
	return s.requiredCheck()
}

// SaveDraft はフォーマットチェックのみを実施します
func (s *StringItem) SaveDraft(value string) error {
	s.Value = value
	return s.formatCheck()
}

```

```shell:実行結果
~/go/go_generics  (main)$ go run main.go
--- Setting 'Hello' to all items ---
Error: item[1]: invalid number format
Error: item[2]: value must be one of [Apple Banana Cherry]

--- Current values ---
Item[0]: Hello
Item[1]: 0.000000
Item[2]: Hello

--- Setting valid values ---

--- Final values ---
Item[0]: Hello
Item[1]: 123.450000
Item[2]: Apple

--- Testing SaveDraft ---
Item[0] SaveDraft OK
Item[1] SaveDraft Error: invalid number format
Item[2] SaveDraft Error: value must be one of [Apple Banana Cherry]

--- Testing Save ---
Item[0] Save OK
Item[1] Save Error: invalid number format
Item[2] Save Error: value must be one of [Apple Banana Cherry]
```