type
Goではtypeを用いて既存の型を拡張した独自の型を定義できる。
呼び出す際に,型が適合していないとコンパイルエラー。
type ID int
type Priority int
func ProcessTask(id ID, priority Priority) {
}
var id ID = 3
var priority Priority = 5
ProcessTask(priority, id) // コンパイルエラー
構造体(struct)
GOの構造体はメソッドを持つことができ,RubyやJavaでのクラスに近い。
構造体もtypeを用いて宣言し,構造体名のあとにそのフィールドを記述する。
各フィールドの可視性は名前で決まり,大文字で始まる場合はパブリック,小文字はプライベート。
type Task struct {
ID int // public
Detail string // public
done bool // private
}
func main() {
var task Task = Task{
ID: 1,
Detail: "buy the milk",
done: true,
}
fmt.Println(task.ID) // 1
fmt.Println(task.Detail) // "buy the milk"
fmt.Println(task.done) // true
task := Task{} //構造体の生成時に値を明示的に指定しなかった場合は,ゼロ値で初期化
}
コンストラクタ
Goにはコンストラクタがない。
代わりにNewで始まる関数を定義し,その内部で構造体を生成するのが通例。
func NewTask(id int, detail string) *Task {
task := &Task{
ID: id,
Detail: detail,
done: false,
}
return task
}
func main() {
task := NewTask(1, "buy the milk")
// &{ID:1 Detail:buy the milk done:false}
fmt.Printf("%+v", task)
}
メソッド
型にはメソッドを定義できます。メソッドは,そのメソッドを実行した対象の型をレシーバとして受>け取り,メソッドの内部で使用できます。
つまりいわゆる拡張メソッドのこと。
func (メソッドを定義する型名の変数名, メソッドを定義する型名) メソッド名 戻り値 {
}
package main
import (
"fmt"
)
type Task struct {
ID int
Detail string
done bool
}
func NewTask(id int, detail string) *Task {
task := &Task{
ID: id,
Detail: detail,
done: false,
}
return task
}
func (task Task) String() string {
str := fmt.Sprintf("%d) %s", task.ID, task.Detail)
return str
}
func main() {
task := NewTask(1, "buy the milk")
fmt.Printf("%s", task.String()) // 1) buy the milk
}
インタフェース
インタフェースの名前は,実装すべき関数名が単純な場合は,その関数名にerを加えた名前を付ける慣習がある。
Goは,型がインタフェースに定義されたメソッドを実装していれば,インタフェースを満たしているとみなす。
↑のTaskにはString()メソッドを実装しているため,Stringerを引数に取る次のような関数に渡すことができる。
type Stringer interface {
String() string
}
~省略~
func main() {
task := NewTask(1, "buy the milk")
fmt.Printf("%s", task.String()) // 1) buy the milk
Print(task) // TaskにはStringメソッドを定義しているためエラーにならない
}
func Print(stringer Stringer) {
fmt.Println(stringer.String())
}
次のような何も指定していないインタフェースを定義すると、どんな型も受け取ることができる関数を定義できる。
定義しなくてもinterface{}と記述すると同じ意味になる。
func Do(e Any) {
// do something
}
Do("a") // どのような型も渡すことができる
func Do(e interface{}) {
// do something
}
Do("a") // どのような型も渡すことができる
型の埋め込み
Goでは,継承はサポートされていない代わりに、ほかの型を「埋め込む」(Embed)という方式で,構造体やインタフェースの振る舞いを拡張できる。
package main
import (
"fmt"
)
type User struct {
FirstName string
LastName string
}
func (u *User) FullName() string {
fullname := fmt.Sprintf("%s %s",
u.FirstName, u.LastName)
return fullname
}
func NewUser(firstName, lastName string) *User {
return &User{
FirstName: firstName,
LastName: lastName,
}
}
type Task struct {
ID int
Detail string
done bool
*User // Userを埋め込む
}
func NewTask(id int, detail,
firstName, lastName string) *Task {
task := &Task{
ID: id,
Detail: detail,
done: false,
User: NewUser(firstName, lastName),
}
return task
}
func main() {
task := NewTask(1, "buy the milk", "Jxck", "Daniel")
// TaskにUserのフィールドが埋め込まれている
fmt.Println(task.FirstName)
fmt.Println(task.LastName)
// TaskにUserのメソッドが埋め込まれている
fmt.Println(task.FullName())
// Taskから埋め込まれたUser自体にもアクセス可能
fmt.Println(task.User)
fmt.Println(task.User.FirstName) //Userからもメンバにアクセス可能
/*
type Task struct {
ID int
Detail string
FirstName string
done bool
*User // Userを埋め込む
}
もしTaskにUser配下のメンバと同名のメンバがいたらどうなるか?
→エラーとはならず共存する
fmt.Println(task.FirstName)
fmt.Println(task.User.FirstName)
*/
}