goで継承をつかう
はじめに
goを勉強始めるとjavaなどで学習してきたオブジェクト指向の考え方を違う場面に出くわす。
たとえば、goでは継承できないことである。
しかし委譲を使えば、同じことを実現できるのでその整理をしようと思う。
仕様整理
下図のような関係を考える。
Personクラスはスーパークラスで、Manクラスをそのサブクラスという関係である。
Personクラスは、名前と年齢をメンバ変数にもち、getName()で名前を取得でき、oldAge()でひとつ年を増やせるメソッドをもつ。
一方Manクラスは、性別("M", "S")をもち、男ならばcall()メソッドで"Mr. 名前"と返す。
goで実装
goではクラス/メソッドではなく、struct/receiverと呼ぶが、クラスを使う。
package main // スーパークラス type Person struct { name string age int } // コンストラクタ func newPerson(name string, age int) *Person { return &Person{ name, age, } } func (p *Person) getName() string { return p.name } func (p *Person) oldAge() { p.age = p.age + 1 } // サブクラス type Man struct { // (1) Personポインタ *Person sex string } // コンストラクタ func NewMan(name string, age int) *Man { return &Man{ // (2) ポインタ変数にインスタンスを格納 Person: newPerson(name, age), sex: "M", } } func (m *Man) call() string { return "Mr." + m.name }
(1) Personポインタを渡すことでManインスタンスから直にPersonインスタンスにアクセスできる
。
man.Person.getName()
ではなく、man.getName()
が可能となる。
Personの部分をショートカットしてgetName()が呼べるので、あたかも継承してるかのように使える。
もし、person Person
とメンバ変数名を明記すると、man.Person.getName()
でアクセスしないといけないので継承ぽさは消える。
(2) サブクラスでスーパクラスのインスタンスを作成するとき、*を外したPersonにインスタンスを格納する。
testコードでうまくいくことを確認できる
package main import "testing" func TestNewMan(t *testing.T) { t.Run("スーパクラスのメソッドgetName()が呼べる", func(t *testing.T) { man := NewMan("adam", 10) if man.getName() != "adam" { t.Fatal("err") } }) t.Run("スーパクラスのメソッドoldAge()が呼べる", func(t *testing.T) { man := NewMan("adam", 10) man.oldAge() if man.age != 11 { t.Fatal("err") } }) t.Run("サブクラスからスーパークラスのメンバ変数nameにアクセスできる", func(t *testing.T) { man := NewMan("adam", 10) if man.name != "adam" { t.Fatal("err") } }) t.Run("サブクラスの定義メソッドcall()が呼べてMr.adamと返す", func(t *testign.T) { man := NewMan("adam", 10) if man.call() != "Mr.adam" { t.Fatal("err") } }) }
PASS ok github.com/karuta0825/study 0.262s
最後に
オブジェクト指向脳の人が、goで継承ぽいものを使う方法を説明しました。
goはコンパイル言語なのに、スクリプト言語のように開発すすめるのはいい言語だなと思います。
javaのライブラリ管理とか面倒だもの。
もっともgenericsが使えないのは、悔しいのですが。。。
そんな感じでgoらしさというのがまだまだわからずなのですが、来年はgoへの理解を深めていきたい!