はじめに
Goはオブジェクト指向の言語ですか?という問いが公式ドキュメントのFAQに載っています。
Is Go an object-oriented language?
Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. ...
つまり、従来のオブジェクト指向言語とはちょっと違う(型の階層はない)けど、オブジェクト指向っぽく書くことはできるということです(私はそう理解しました)。
そこで、Goでオブジェクト指向っぽく書いてみました。
カプセル化
Goでは、構造体やそのフィールド、関数、メソッドのスコープは、名前の先頭が大文字か小文字かで決まります。
大文字ならpublic、小文字なら同じパッケージ内に閉じたスコープとなります。
したがって、以下のように書くことでカプセル化を実現できます。
package class
type Human struct {
// フィールド名を小文字からはじめてスコープを同じパッケージ内に閉じる
name string
}
// コンストラクタ
// 構造体がpublicでも、そのフィールドが閉じたスコープの場合、
// パッケージ外から{}を使った初期化はできない。
func NewHuman(name string) *Human {
return &Human{ name: name }
}
// カプセル化
// メソッド名を大文字からはじめることで、publicなスコープにする。
func (human Human) GetName() string {
return human.name
}
// セッターはレシーバをポインタにしないと値が変更されない
func (human *Human) SetName(name string) {
human.name = name
}
package main
import (
"./class"
"fmt"
)
func main() {
human := class.NewHuman("alice")
fmt.Println(human.GetName())
human.SetName("bob")
fmt.Println(human.GetName())
}
$ go run main.go
alice
bob
注意
- human.goでコンストラクタと書きましたが、Goにはコンストラクタにあたる構文がありません。代わりに、Newから始まる関数を定義して、その内部で構造体を生成するのが通例です。
- 同じパッケージ内からはアクセッサを経由しないでアクセスできてしまいます。
Embed(埋め込み)
Goに継承はありませんが、代わりに他の型を埋め込む(Embed)方式で振る舞いを拡張できます。
埋め込まれた型(以下ではHuman)のフィールドやメソッドは、埋め込んだ型(以下ではMan)が実装しているかのように振る舞います。
/* 略 */
// Humanのメソッド
func (human Human) Check() {
fmt.Printf("%s is a Human.\n", human.name)
}
type Man struct {
*Human // Humanを埋め込む
}
func NewMan(name string) *Man {
return &Man{ Human: NewHuman(name) }
}
/* 略 */
func main() {
man := class.NewMan("bob")
man.Check() // Humanのメソッドを自分が実装しているかのように呼び出すことができる。
}
$ go run main.go
bob is a Human.
オーバーライド
同名のメソッドを自分の型をレシーバにして再定義することでメソッドをオーバーライドすることができます。
/* 略 */
func (man Man) Check( ) {
fmt.Printf("%s is a Man.\n", man.name)
}
先ほどと同じmain.goを実行すると、
$ go run main.go
bob is a Man.
注意
埋め込んだ型(Man)を埋め込まれた型(Human)として扱うことはできません。例えば、
var man *class.Human = class.NewMan("bob")
とするとコンパイルエラーになります。
これは最初に載せた公式ドキュメントにもある通り、Goには型の階層がなくHumanとManは完全に別の型扱いになるからです。
Embedはあくまで埋め込みであって継承ではありません。
ポリモフィズム
Goでは継承を使ったポリモフィズムは実現できませんが、interfaceを実装することでポリモフィズムを実現することができます。
/* 略 */
type Speaker interface {
Speak()
}
/* 略 */
// HumanにSpeakerを実装
func (human Human) Speak() {
fmt.Println("I am Humman.")
}
/* 略 */
// Humanは埋め込んでいない
type Woman struct {
name string
}
func NewWoman(name string) *Woman {
return &Woman{ name: name }
}
// WomanにSpeakerを実装
func (woman Woman) Speak() {
fmt.Println("I am Woman.")
}
/* 略 */
func main() {
human := class.NewHuman("alice")
woman := class.NewWoman("emily")
// HumanもWomanもSpeakerインターフェースとして扱うことができる。
speak(human)
speak(woman)
}
func speak(speaker class.Speaker) {
speaker.Speak()
}
$ go run main.go
I am a Humman.
I am a Woman.
interfaceを実装した型を埋め込む
あるinterfaceを実装した型を埋め込んだ構造体もまた、そのinterfaceとして扱うことができます。
上の例では、ManはHumanを埋め込んでいるので、Man自体はSpeakerを実装していなくてもSpeakerとして扱うことができます。
/* 略 */
func main() {
human := class.NewHuman("alice")
man := class.NewMan("bob")
woman := class.NewWoman("emily")
speak(human)
speak(man)
speak(woman)
}
func speak(speaker class.Speaker) {
speaker.Speak()
}
$ go run main.go
I am a Humman.
I am a Humman.
I am a Woman.
もちろんManがSpeakメソッドをオーバーライドすることも可能です。
まとめ
以上のように、Goでもある程度オブジェクト指向っぽく書くことができました。