
構造体の型が違うデータを一緒に扱う方法ってないのかな?
今回は型の異なる構造体を共通に扱う方法についてお伝えしていきます。
Go言語では、interface型を利用することで違う構造体の型でも共通に扱うことができます。
簡単なプログラムを例にご紹介していきましょう。
interface型を利用して型が違う構造体を一緒に扱う方法
いきなり完成形のプログラムを見ると混乱してしまうので、まずは基本のプログラムから徐々にinterface型を利用して型が違う構造体を共通化していく流れを追っていきましょう。
プログラムの基本形
今回はロールプレイングゲームのプレイヤーのデータを扱うプログラムを例にします。
違う型の構造体をinteface型で共通化するためには、2つ以上の構造体が必要になりますので今回は構造体を2つ用意します。
// プログラムの基本形
package main
type Player struct {
Name string // プレイヤーの名前
Profession string // プレイヤーの職業
}
type Status struct {
Level int // レベル
Hp int // HP
Mp int // MP
}
func main() {
}
画面に出力するためのメソッドを定義
現在のプレイヤーの状況を画面に出力するためのメソッドを定義いたします。
// メソッドを定義
package main
type Player struct {
Name string // プレイヤーの名前
Profession string // プレイヤーの職業
}
// Player型内容を出力するメソッド
func (p *Player) playerData() string {
return fmt.Sprintf("名前は%v 職業は%vです", p.Name, p.Profession)
}
type Status struct {
Level int // レベル
Hp int // HP
Mp int // MP
}
// Status型内容を出力するメソッド
func (s *Status) playerData() string {
return fmt.Sprintf("Level:%v HP:%v MP:%v", s.Level, s.Hp, s.Mp)
}
func main() {
}
ここでのポイントは、プログラムの実行を一回で処理するために2つのメソッド名を共通のものにします。
interface型を利用して共通化
それでは、今回の目的となるinteface型を利用した、異なる構造体を共通化するためのコードを記述つします。
// interface型を利用して共通化
package main
// interface型で共通化
type DataAll interface{
playerData() string
}
type Player struct {
Name string // プレイヤーの名前
Profession string // プレイヤーの職業
}
// Player型内容を出力するメソッド
func (p *Player) playerData() string {
return fmt.Sprintf("名前は%v 職業は%vです", p.Name, p.Profession)
}
type Status struct {
Level int // レベル
Hp int // HP
Mp int // MP
}
// Status型内容を出力するメソッド
func (s *Status) playerData() string {
return fmt.Sprintf("Level:%v HP:%v MP:%v", s.Level, s.Hp, s.Mp)
}
func main() {
}
ここでのポイントは、共通化するためにinteface型を定義する時にフィールドに共通のメソッド名 playerData() string
を定義していることです。
DataAll
とした型名を利用して共通化をしていきます。
異なる型の構造体をひとつのスライスにまとめる
通常であれば、異なる型の構造体をひとつのスライスにまとめることはできません。
しかし、今回のようにinterface型を活用すると実現できます。
// 異なる型の構造体をひとつのスライスにまとめる
package main
// interface型で共通化
type DataAll interface{
playerData() string
}
type Player struct {
Name string // プレイヤーの名前
Profession string // プレイヤーの職業
}
// Player型内容を出力するメソッド
func (p *Player) playerData() string {
return fmt.Sprintf("名前は%v 職業は%vです", p.Name, p.Profession)
}
type Status struct {
Level int // レベル
Hp int // HP
Mp int // MP
}
// Status型内容を出力するメソッド
func (s *Status) playerData() string {
return fmt.Sprintf("Level:%v HP:%v MP:%v", s.Level, s.Hp, s.Mp)
}
func main() {
// 異なる構造体をひとつのスライスにまとめる
user1 := []DataAll {
&Player{Name: "もりぴ", Profession: "勇者"},
&Status{Level: 10, Hp: 100, Mp: 50},
}
// for文で値を出力
for _, v := range user1 {
// playerData()をここで実行
fmt.Println(v.playerData())
}
}
// 出力結果
名前はもりぴ 職業は勇者です
Level:10 HP:100 MP:50
fmt.Sprintf()
関数の内容を一気に処理することができました。
今回使用した DataAll
はinterface型の「あらゆる型との互換性」という特性を活かして、型の違う構造体でもスライスとして定義と初期化まで行うことができます。

名称は DataAll
でなくてもいいですよ!
値渡しでも同様の結果が得られます
今回は、ロールプレイングゲームで職業が転職したりレベルアップするため値が変更されることを前提に参照渡しで作成しましたが、もちろん値渡しでも同じ結果を得ることができます。
// 値渡し
package main
// interface型で共通化
type DataAll interface{
playerData() string
}
type Player struct {
Name string // プレイヤーの名前
Profession string // プレイヤーの職業
}
// Player型内容を出力するメソッド
func (p Player) playerData() string {
return fmt.Sprintf("名前は%v 職業は%vです", p.Name, p.Profession)
}
type Status struct {
Level int // レベル
Hp int // HP
Mp int // MP
}
// Status型内容を出力するメソッド
func (s Status) playerData() string {
return fmt.Sprintf("Level:%v HP:%v MP:%v", s.Level, s.Hp, s.Mp)
}
func main() {
// 異なる構造体をひとつのスライスにまとめる
user1 := []DataAll {
Player{Name: "もりぴ", Profession: "勇者"},
Status{Level: 10, Hp: 100, Mp: 50},
}
// for文で値を出力
for _, v := range user1 {
// playerData()をここで実行
fmt.Println(v.playerData())
}
}
// 出力結果
名前はもりぴ 職業は勇者です
Level:10 HP:100 MP:50
絶対に構造体のデータを書き換えられたくない場合は値渡しを利用しましょう。

アプリ開発の状況に応じて、値渡しか参照渡しかを判断していきましょう。
【まとめ】異なる構造体の型を共通に扱う
今回は型の違う構造体をinterface型を活用して共通化する方法をお伝えしてきました。
それでは、まとめにはいりましょう。
以上です。
構造体をこまめに分割して管理する場合にとても有効な方法です。
まずは、構造体を要素にしたスライスの定義方法を理解することも大事なので、合わせて押さえておきましょう。
comment