0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Go]メソッド

Last updated at Posted at 2023-08-22

概要

  • 前回の続き
  • 今回はメソッドについて学習
    • 教材を読むに、次のインターフェースと合わせて、特に重要な単元のようだ。

参考

メソッド

  • ユーザ定義の型に付随する関数としてメソッドが定義できる
  • 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等のオブジェクト指向との共通点をあまり感じなかった。)
  • そのため、本単元をいまいち理解しきれていない。次のインターフェースでも引き続き本単元の内容を利用するようなので、そこで理解を深めたい。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?