概要
- 前回の続き
- 今回はメソッドについて学習
- 教材を読むに、次のインターフェースと合わせて、特に重要な単元のようだ。
参考
メソッド
- ユーザ定義の型に付随する関数としてメソッドが定義できる
-
func (レシーバ) メソッド名(引数) 戻り値
の形式- 関数にレシーバが追加されている。レシーバは紐付けるユーザ定義型のこと
- Javaとは異なり、オブジェクトとは別の場所で定義しており、紐付にレシーバを使っている。
- また、オーバロードはできないため、一つのレシーバに対して同じメソッド名は複数定義できない
// ユーザ定義型(構造体)
type Person struct {
name string
age int
}
// メソッド
func (p Person) toString() string {
return fmt.Sprintf("%s: %d歳", p.name, p.age)
}
func main() {
person := Person {
name: "test",
age: 30,
}
// メソッドの実行
fmt.Println(person.toString())
}
ポインタ型と値型レシーバ
- ポインタ型レシーバは、レシーバが変更される場合に指定する
- 値型レシーバは、レシーバを変更しない
- 関数の引数のポインタと同じ考え
- ユーザ定義型に対するメソッドの内、ポインタ型レシーバがひとつでもあれば、他のメソッドもポインタ型レシーバにする必要がある。
- →何故そうする必要があるのか?ひとまず、そういるルールとして覚えておくか。
type Counter struct {
total int
updated time.Time
}
// ポインタ型レシーバ
func (c *Counter) Incremament() {
c.total++
c.updated = time.Now()
}
func main() {
var c Counter
c.Incremament() // 暗黙的にポインタ値に変換されるため、&cではなくてOK
}
メソッド値とメソッド式
- メソッド値:メソッドを変数に格納できる(=第一級オブジェクト)
- メソッド式:型から関数を作成できる。
func(レシーバ, 引数) 戻り値型
。第一引数にはメソッドのレシーバを設定する。`
type Adder struct {
value int
}
func (a Adder)AddTo(val int)int {
return a.value + val
}
func main() {
myAdder := Adder{value: 10}
// メソッド値
f1 := myAdder.AddTo
fmt.Println((f1(20)))
// メソッド式
f2 := Adder.AddTo
fmt.Println(f2(myAdder,25))
}
iota
- Goは列挙型はなく、代わりにiotaというものがある。
- iota:増加する値を一連の定数に割り当てることができる。
-
const (定数名 型=iota)
を指定し、2行目以降は定数名を列挙
// 型を定義
type Category int
// iota値は0からの連番
const (
Cate1 Category = iota //0
Cate2 //1
Cate3 //2
)
埋め込みによる合成
- Goには継承がないが、代わりに合成や昇格が組み込まれており、これを使用して、コードの再利用性を推奨している。
- 昇格
- 下位の構造体を、上位の構造体のフィールドを埋め込むと、下位の構造体は上位の構造体に昇格する。
- 昇格すると、上位の構造体から直接呼び出すことができる
// 下位の構造体
type Employee struct {
name string
id string
}
func (e Employee) Description() string {
return fmt.Sprintf("%s(%s)", e.name, e.id)
}
// 上位の構造体
type Manager struct {
Employee //昇格:下位の型のみを書く。型のフィールドが加わる。
Reports []Employee
}
func (m Manager) addEmpoyees() []Employee {
newEmployees := []Employee{
{"太郎","100"},
{"治郎","200"},
}
return newEmployees
}
func main() {
m := Manager{
Employee: Employee{"花子", "000"},
Reports: []Employee{},
}
// 下位の型のフィールド、メソッドを呼び出せる
fmt.Println(m.id) // 000
fmt.Println(m.Description()) //000(花子)
m.Reports = m.addEmpoyees()
fmt.Println(m.Employee) // {花子 000}
fmt.Println(m.Reports) // [{太郎 100} {治郎 200}]
}
埋め込みと継承の違い
- 埋め込みをサポートしている言語はGo言語だけらしい
- 継承とは異なり、サブクラス(上記のManager)を、親クラス(上記のEmployee)に代入できない。
- ※説明のためにサブクラス、親クラスとしたが、継承ではないので実際は違う。
- 具象型には、動的ディスパッチが存在しない
- 実行時の型によって呼び出し先が変わること。
- オーバライドやポリモーフィズムの仕組みが働かないということ?
type Inner struct {
A int
}
func (i Inner) IntPrinter(val int) string {
return fmt.Sprintf("Inner: %d", val)
}
func (i Inner) Double() string {
result := i.A * 2
// ここで呼び出されるのは、innerのIntPrinter
return i.IntPrinter((result))
}
type Outer struct {
Inner // 埋め込み
S string
}
func (o Outer) IntPrinter(val int) string {
return fmt.Sprintf("Outer: %d", val)
}
func main() {
o := Outer{
Inner: Inner{
A: 10,
},
S: "Hello",
}
fmt.Println(o.Double()) //Inner: 20
}
終わりに
- メソッドは構造体の内部で定義するのではなく、外部で定義する。その際に構造体をレシーバとして紐づけている。
- この辺りの構文の違いから、明確にオブジェクト指向とは違うのだと感じた。
- 埋め込みは継承と異なり、型の互換性はない。オーバライドやポリモーフィズムもなさそう。(言及はされていなかったので不明)
- Javaなどのコンポジション(has-a関係)とも違うのだろうか。
- メソッド、埋め込み(合成)は、Goの独自色が強いと感じた。(Java等のオブジェクト指向との共通点をあまり感じなかった。)
- そのため、本単元をいまいち理解しきれていない。次のインターフェースでも引き続き本単元の内容を利用するようなので、そこで理解を深めたい。