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のポリモーフィズムを活用!複数の型を1つのスライスで扱う方法

Posted at

はじめに

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]
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?