Go言語の構造体でポリモーフィズム的な設計をする基本例
生徒
「Go言語ってオブジェクト指向ができないって聞いたけど、ポリモーフィズムみたいなことはできないんですか?」
先生
「Go言語では、クラスという考え方はありませんが、構造体(struct)とインターフェース(interface)を使って、ポリモーフィズム的な設計ができますよ。」
生徒
「どういうことですか?簡単な例で教えてください!」
先生
「それでは、構造体とインターフェースを使って、動物たちが鳴くプログラムを作ってみましょう!」
1. ポリモーフィズムとは何か?
まず「ポリモーフィズム」という言葉の意味を簡単に説明します。日本語では「多態性」と訳されます。これは、同じ操作でも、対象によって異なる動作をすることを指します。
例えば「鳴く」という命令があったとき、犬なら「ワン」、猫なら「ニャー」と鳴くような違いです。
2. 構造体とインターフェースで表現する
Go言語では「構造体」でデータを定義し、「インターフェース」で動作(メソッド)をまとめます。これによって、ポリモーフィズム的な設計が実現できます。
type Animal interface {
Speak() string
}
type Dog struct {}
func (d Dog) Speak() string {
return "ワンワン"
}
type Cat struct {}
func (c Cat) Speak() string {
return "ニャー"
}
ここでは、Animalというインターフェースを定義し、DogとCatという構造体が、それぞれSpeak()メソッドを持っています。
3. インターフェース型の変数に代入する
それぞれの動物をAnimal型の変数に代入することで、共通の操作ができるようになります。
func main() {
var a Animal
a = Dog{}
fmt.Println(a.Speak()) // ワンワン
a = Cat{}
fmt.Println(a.Speak()) // ニャー
}
a.Speak()という同じ命令でも、代入されている動物によって違う動作になります。これがポリモーフィズムの基本的な考え方です。
4. 配列やスライスで複数の動物を扱う
インターフェース型のスライスを使うと、異なる種類の構造体を一緒に扱えます。
func main() {
animals := []Animal{Dog{}, Cat{}}
for _, a := range animals {
fmt.Println(a.Speak())
}
}
このようにして、違う種類の動物が混在するスライスでも、Speak()メソッドを共通して使うことができます。
5. インターフェースを使うメリット
構造体だけを使っていると、種類ごとに別々のコードを書かなければならなくなります。インターフェースを使うことで、共通の型として扱えるようになり、コードの再利用性が高まり、柔軟な設計ができるようになります。
Go言語では「型の共有」よりも「振る舞いの共有」を重視して設計されています。そのため、クラスは無くても、構造体とインターフェースを使ってオブジェクト指向的な考え方が実現できるのです。
まとめ
Go言語の構造体とインターフェースを使い、ポリモーフィズム的な設計を行う方法を学んできました。クラスが存在しないGo言語でも、構造体にメソッドを定義し、インターフェースによって共通の振る舞いをまとめることで、多態性を自然に表現できるという特徴は、Goの設計思想の中でも非常に重要なポイントです。今回の記事では、犬と猫がそれぞれ異なる声で鳴くという、初心者でも理解しやすい例を通して、同じ命令でも実際の動作が変わる仕組みについて深く掘り下げました。 ポリモーフィズムは、オブジェクト指向の中核的な考え方であり、複数の型が同じインターフェースを実装することで、共通の操作を統一的に扱えるようになります。Goではクラスを使わない代わりに、構造体とインターフェースを組み合わせることで、柔軟で拡張性の高いプログラムを作ることができます。これにより、新たな種類の動物を追加したい場合も、構造体にメソッドを実装するだけで既存のコードに自然に組み込むことが可能です。 さらに、インターフェース型のスライスを使うことで、異なる種類の構造体を一つの集合として扱い、ループでまとめて処理する設計も非常に便利です。こうした書き方に慣れると、大規模なプログラムで多くの型を扱う場合でも、コードの見通しが良くなり、保守性が飛躍的に向上します。 また、インターフェースの導入によって、コードの依存関係を減らし、テストしやすい設計ができる点も実務で評価されるポイントです。動物が鳴くという例は身近ですが、実際にはログの出力形式を変えたり、複数のデータ保存先を切り替えたりする場面で、この考え方がそのまま活用されます。 以下では、記事の内容を発展させたサンプルコードを掲載し、より深い理解につながるよう、複数の構造体を追加したバージョンも紹介します。インターフェースの使い方や、ポリモーフィズムの効果を確認しながら、自分のプロジェクトにも応用できるよう意識して読み進めてください。
ポリモーフィズム応用サンプルコード
package main
import "fmt"
// インターフェース定義
type Animal interface {
Speak() string
}
// 構造体ごとの実装
type Dog struct {}
func (d Dog) Speak() string { return "ワンワン" }
type Cat struct {}
func (c Cat) Speak() string { return "ニャー" }
type Bird struct {}
func (b Bird) Speak() string { return "チュンチュン" }
// Animal を受け取って動作させる関数
func makeSound(a Animal) {
fmt.Println("鳴き声:", a.Speak())
}
func main() {
animals := []Animal{Dog{}, Cat{}, Bird{}}
for _, a := range animals {
makeSound(a)
}
}
このサンプルでは、犬・猫・鳥の3種類の構造体にそれぞれSpeak()を実装しています。インターフェースAnimalを満たす構造体であれば、どの種類の動物が来てもmakeSound()関数で処理できる点が、ポリモーフィズムの大きな強みです。
実際の業務では、複数の異なる種別を扱うプログラムは非常に多く、Goのインターフェースはその柔軟性を最大限に生かせる仕組みとなっています。今後Goで開発を進める際には、構造体とインターフェースの設計をセットで考える習慣を身につけておくと、より直感的で応用力のあるコードが書けるようになります。
生徒
「構造体とインターフェースだけで、こんなにオブジェクト指向っぽい書き方ができるなんて驚きました!」
先生
「Goはシンプルな言語ですが、インターフェースを使うことで複雑な設計にも対応できるんですよ。特にポリモーフィズムは実務で本当によく使われます。」
生徒
「たしかに、動物を追加するのも簡単で、スライスに入れるだけで同じように扱えるのが便利ですね。」
先生
「そうそう。新しい種類の機能を追加したいときも、構造体を1つ増やしてインターフェースを実装するだけで済むので、変更に強い設計になるんです。」
生徒
「Goはクラスがないって聞いて不安でしたが、今回の仕組みを知ったら逆にスッキリしてて好きかもしれません!」