読者です 読者をやめる 読者になる 読者になる

Go言語入門 / golang 入門 オブジェクト指向の多様性と継承はどうなるのか

Go言語 / golang プログラミング Go言語入門 / golang 入門

f:id:nasust:20161122141513p:plain The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/) CC3.0

こんにちはnasustです。

前回はGo言語 / golangオブジェクト指向について解説したいと思います。 既に解説したようにGo言語 / golangは一般的なオブジェクト指向の機能をサポートしてません。 ではGo言語 / golangで多様性と継承の実現を解説します。

interface型と構造体の埋め込みです。

interface型

interface型はJavaを経験してきた方には分かりやすいかと思います。 interface型はメソッド群を定義するものです。

Javaと違う所は、implements宣言しなくても、interface型で定義してあるめメソッドを実装していれば、interface型を満たしているということになります。 スクリプト言語で使用されているダック・タイピングです。

package main

import "fmt"

type Hello interface {
    Hello()
    World()
}

type Hoge struct {
}

func (hoge *Hoge) Hello() {
    fmt.Print("Hello")
}

func (hoge *Hoge) World() {
    fmt.Print(" World!!")
}

func Print(hello Hello) {
    hello.Hello()
    hello.World()
}

func main() {
    Print(&Hoge{})
}

実行するとHello World!!が表示されます。 Print関数の引数にはHelloという名前のinterface型が指定されています。 main関数でPrint関数でHoge構造体のポインタを指定して実行しています。 Hoge構造体はHelloというinterface型のメソッドを満たしている為に指定することができます。

C++を学んできた方には違和感を感じると思います。Print関数でHello interface型で引数がポインタにしていないにも関わらず、実行時にはポインタを指定しています。

これは、Go言語 / golangの仕様でGoのメソッドのレシーバーがポインタ渡し、値渡しの二通りあります。例のコードの場合、構造体はポイント渡しのレシーバーで実装されています。そのためPrint引数は暗黙的に構造体のポインタを指定するようになっています

逆に構造体が値渡しでレシーバーを実装している場合は、同じPrint関数でも値で指定します。

interface型の引数は、構造体のレシーバーがポインタか値かで暗黙的に引数の渡し方が変化します。

Go言語で基本的には値渡しはコピーとなるので、それだけオーバーヘッドになるので、実際開発する場合は、暗黙的にポインタを指定すると覚えておけば良いと思います。

余談ですが、C++のクラスのメソッドの呼び出しは、値の場合、.で、ポインタの場合、->です。Goはポインタのメソッド呼び出しも暗黙的に.で実行できるようになっています。

構造体の埋め込み

Go言語 / golangには継承はありません。ですが、構造体の埋め込みを使用することで同じような事ができます。RubyのMIXIN機能に似ています。

package main

import "fmt"

type Hoge struct {
   HogeMenber1 string
   HogeMenber2 string
}

func (hoge *Hoge) HogePrint() {
   fmt.Println("Hoge: ", hoge.HogeMenber1, ":", hoge.HogeMenber2)
}

type Fuga struct {
   FugaMenber1 string
   FigaMenber2 string
}

func (fuga *Fuga) FugaPrint() {
   fmt.Println("Fuga: ", fuga.FugaMenber1, ":", fuga.FigaMenber2)
}

type HogeFuga struct {
   *Hoge
   *Fuga
}

func NewHogeFuga() *HogeFuga {
   hogeFuga := &HogeFuga{
       Hoge: &Hoge{"member1", "member2"},
       Fuga: &Fuga{"member1", "member2"},
   }
   return hogeFuga
}

func main() {
   hogeFuga := NewHogeFuga()
   hogeFuga.HogePrint()
   hogeFuga.FugaPrint()
}

:=は型推測で変数を宣言しています。

これを実行すると、

Hoge:  member1 : member2
Fuga:  member1 : member2

が表示されます。構造体の埋め込みを使用することで、構造体同士の合体が出来て、オブジェクト指向の継承っぽいことができる事が理解出来たでしょうか。

因みに、構造体同士でメソッドが同じ定義で衝突した場合は、明示的に埋め込んだ構造体を指定して実行します。

例: hogeFuga.Hoge.HogePrint() ですのでオーバーロードはできませんが、同じ定義のメソッドを持っても問題なく実行できます。衝突した場合、Hogeを省略することは出来ません。コンパイルエラーとなります。

まとめ

interface型と構造体の埋め込みでオブジェクト指向っぽい事が出来ることを理解 できたでしょうか。このようにシンプルなGo言語の構文で組み合わせることによって、色々なプログラムの構造を実現することが出来ます。


Go言語 / golang 入門 目次に戻る

広告を非表示にする