異なる構造体の型をinterfaceで共通に扱うGo言語での方法

Go言語
この記事は約8分で読めます。
Go初学者
Go初学者

構造体の型が違うデータを一緒に扱う方法ってないのかな?

この記事では構造体の型が違うデータを一緒に扱う方法が理解できます!
  • interface型を利用して型が違う構造体を一緒に扱う方法について
    1. 型の違う構造体をひとつのスライスにまとめる
    2. 型の違う構造体に同じ処理のメソッドを一度に処理する方法について

今回は型の異なる構造体を共通に扱う方法についてお伝えしていきます。

Go言語では、interface型を利用することで違う構造体の型でも共通に扱うことができます。

簡単なプログラムを例にご紹介していきましょう。

もりぴ
この記事を書いた人

XHTML1.0時代にHTML&CSSを勉強した経験あり。無趣味だった私が2020年5月からプログラミング学習を開始し現在も挫折せずに趣味で学習を楽しんでいる51歳。プログラミングの楽しさをブログを通してお伝えしていきます。

もりぴをフォローする

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型を活用して共通化する方法をお伝えしてきました。

それでは、まとめにはいりましょう。

この記事で押さえておくポイント!
  • 異なる構造体の型はinterface型を利用して共通化できる
  • 共通化して同じ処理をする時はメソッド名も共通化する
  • interface型のフィールドをメソッド名にする
  • interface型を宣言した時の型名を利用してひとつのスライスにまとめる
  • 値渡しでも参照渡しでも同様に共通化できる

以上です。

構造体をこまめに分割して管理する場合にとても有効な方法です。

まずは、構造体を要素にしたスライスの定義方法を理解することも大事なので、合わせて押さえておきましょう。

comment

タイトルとURLをコピーしました