【自己学習用】はじめてのGo3


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)
    */
}