社内でGo勉強会をやったのでその時に自分が発表した時の内容。オブジェクト指向的なことをGoでどう実現するのか、どの辺を諦めなくてはいけないのかをまとめてみた。
Goでのオブジェクト指向
Goでは基本的にオブジェクト指向プログラミングはできないと思ったほうが良い。できるのはstructを定義して関数を追加していくことだけである。
http://play.golang.org/p/7w9nbHDec1
package main
import "fmt"
type User struct {
Id int
Name string
}
func (u *User) String() string {
return fmt.Sprintf("%d:%s", u.Id, u.Name)
}
func main() {
var user *User = &User{1, "oinume"}
fmt.Println(user.String())
}
継承っぽいことを実現するには
埋め込み構造体を使えばクラスの継承っぽいことはできる。ただし、継承という仕様はないため型の互換性はない。Mixinに近い感じ。
http://play.golang.org/p/5wHW90Zvs6
package main
import (
"fmt"
)
type User struct {
Id int
Name string
}
func (u *User) String() string {
return fmt.Sprintf("%d:%s", u.Id, u.Name)
}
type BannedUser struct {
User
BannedReason string
}
func (u *BannedUser) GetBannedReason() string {
return u.BannedReason
}
type PBannedUser struct {
*User
BannedReason string
}
func (u *PBannedUser) GetBannedReason() string {
return u.BannedReason
}
func main() {
user := BannedUser{User{1, "oinume"}, "Too many spam"}
fmt.Println(user.Name)
puser := PBannedUser{&User{1, "oinume"}, "Too many spam"}
fmt.Println(puser.Name)
fmt.Println(puser.User.Name)
fmt.Println(puser.String())
}
型の定義とキャスト
以下のようにキャストしても、呼び出されるのはReadOnlyDatabaseの関数ではなく、Databaseのものであることに注意。
http://play.golang.org/p/2U5NVaYKb_
package main
import "fmt"
type Database struct {
}
func (d *Database) NewSession() *Session {
return &Session{}
}
type Session struct {
}
func (s *Session) Connect() string {
return "Session.Connect()"
}
type ReadOnlyDatabase Database
func (d *ReadOnlyDatabase) NewSession() *ReadOnlySession {
return &ReadOnlySession{}
}
type ReadOnlySession Session
func (s *ReadOnlySession) Connect() string {
return "ReadOnlySession.Connect()"
}
func main() {
var database *Database = &Database{}
var session *Session = database.NewSession()
fmt.Println(session.Connect())
var roDataBase *ReadOnlyDatabase = &ReadOnlyDatabase{}
var roSession *Session = (*Session)(roDataBase.NewSession())
fmt.Println(roSession.Connect())
var ros *ReadOnlySession = roDataBase.NewSession()
fmt.Println(ros.Connect())
}
Javaだと下記はReadOnlySession.Connect()のメソッドが呼ばれる。
var roSession *Session = (*Session)(roDataBase.NewSession())
roSession.Connect()
http://golang.org/doc/faq#How_do_I_get_dynamic_dispatch_of_methods
The only way to have dynamically dispatched methods is through an interface. Methods on a struct or any other concrete type are always resolved statically.
interfaceとduck typing
Goでは型の継承という仕様はないが、interfaceというものがある。interfaceは単にメソッドを記述したものである。そのメソッドを実装しているstructはそのinterfaceを満たしていると言える。
package main
import "fmt"
type JSONable interface {
JSON() string
}
type User struct {
Id int
Name string
}
func (s *User) JSON() string {
return fmt.Sprintf(`{ "Id": %d, "Name": "%s" }`, s.Id, s.Name)
}
type AdminUser struct {
User
Admin bool
}
func (s *AdminUser) JSON() string {
return fmt.Sprintf(`{ "Id": %d, "Name": "%s", "Admin": %v }`, s.Id, s.Name, s.Admin)
}
func main() {
var user JSONable = &User{1, "oinume"}
fmt.Println(user.JSON())
var adminUser JSONable = &AdminUser{User{0, "admin"}, true}
fmt.Println(adminUser.JSON())
jsonable, ok := adminUser.(JSONable)
if ok {
fmt.Printf("JSON(): %s\n", jsonable.JSON())
} else {
fmt.Printf("Not JSONable\n")
}
}
Duck Typingとは
「アヒルのように鳴くならアヒル」
Rubyとかの動的言語でよく使われる。「そのメソッドが呼び出せるのならインタフェースを満たしている」
上の例だと、UserやAdminUserはJSON()メソッドを実装しているのでJSONableを実装していると言える。
所感
Goでは継承という概念がないので、structの型の互換性を考えるよりもinterfaceベースで型を考えないとダメ。
参考資料
http://jxck.hatenablog.com/entry/20130325/1364251563 が非常に参考になりました。