はじめに
Goで複数の型を1つのスライスで扱う方法を記載します。
ソースコード
ディレクトリ構成
~/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
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)
}
}
}
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
}
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()
}
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()
}
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()
}
実行結果
~/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]