GO言語でクラスっぽいことをする

**2020/11/06 追記**
語弊のある言い回しを修正しました。
コメント欄により詳しい情報が集まっておりますので、興味のある方はご覧ください。

---

ディップ株式会社に中途入社してGo言語でAPIを作ってます。
今までPHPやRubyなんかのゆるっとしたオブジェクト指向言語がメインだったので毎日ヒーヒー言ってます。

そんな中でも前職の経験を活かせる機会があったので
今回は「前職の経験を活かしてGo言語のソースをいい感じにした」話をしようと思います。

## Go言語にクラスはない

Go言語を扱っている方は当然ご存知かと思いますが、Go言語にクラスなんてありません。
今まで培ってきたクラスを活用するオブジェクト思考（誤字ではない）がまるでゴミのようです。

Goで実装するなら素直にGoっぽい（オブジェク指向を使わない）実装をすべきなのでしょうが、そうは言っても「これクラスがあったらこう実装するのに」という考えがよぎるもの。
未練がましくネットで検索したところ「Go言語でオブジェクト指向っぽいことをする方法」がいくつか出てきました。
それらを参考にいい感じの実装ができたので、サンプルと共にまとめておきます。

## やりたいこと

* プロパティに値を保持する
* コンストラクタで初期設定を行う
* 共通のプロパティを定義する
* 共通のメソッドを定義する
* 継承した子クラスに独自のプロパティを持たせる
* 継承した子クラスに独自のメソッドを持たせる
* 継承した子クラスで振る舞いを変える

オブジェクト指向と言っても色々ありますが、今回はこの辺を目指します。
カプセル化やポリモフィズムに関してはここでは省略します。

## 実装例

```go
package main

import (
	"fmt"
)

const line = "--------------------"

func main() {
	wiz := newWizard("魔法少女", 10, 10, 5)
	war := newWarrior("+‡†狂戦士†‡+", 10, 15, 30)

	fmt.Println(wiz.hello())
	fmt.Println(war.hello())

	fmt.Println(line)
	fmt.Println(wiz.attack())
	fmt.Println(war.attack())

	fmt.Println(line)
	fmt.Println(wiz.magic())
	fmt.Println(war.attack())
}

type human struct {
	name string
	hp int
	ap int
}

func (h *human) init(name string, hp, ap int) {
	h.name = name
	h.hp = hp
	h.ap = ap
}

func (h *human) hello() string {
	return fmt.Sprintf("こんにちは、私は%sです。", h.name)
}

func (h *human) attack() string {
	return fmt.Sprintf("%sの攻撃！%dのダメージ！", h.name, h.ap)
}

type wizard struct {
	human
	mp int
}

func newWizard(name string, hp, ap, mp int) *wizard {
	w := new(wizard)
	w.init(name, hp, ap)
	w.mp = mp
	return w
}

func (w *wizard) magic() string {
	if w.mp <= 0 {
		return fmt.Sprintf("%sは力がでない", w.name)
	}
	w.mp -= 1
	return fmt.Sprintf("%sは魔法を使った！30のダメージ！", w.name)
}

type warrior struct {
	human
}

func newWarrior(name string, hp, ap, mp int) *warrior {
	w := new(warrior)
	w.init(name, hp, ap)
	return w
}

func (w *warrior) attack() string {
	return fmt.Sprintf("%sの攻撃！%dのダメージ！", w.name, w.ap*2)
}
```

実行結果

```
こんにちは、私は魔法少女です。
こんにちは、私は+‡†狂戦士†‡+です。
--------------------
魔法少女の攻撃！10のダメージ！
+‡†狂戦士†‡+の攻撃！30のダメージ！
--------------------
魔法少女は魔法を使った！30のダメージ！
+‡†狂戦士†‡+の攻撃！30のダメージ！
```

よくあるRPGの職業的なやつ

## 実装内容の説明

### 親クラス（構造体）

```go
type human struct {
	name string
	hp int
	ap int
}
```

共通で使うプロパティを定義しておく

### 親クラス（構造体）のコンストラクタ

```go
func (h *human) init(name string, hp, ap int) {
	h.name = name
	h.hp = hp
	h.ap = ap
}
```

Goには構造体というものがないので、コンストラクタの仕事をさせるメソッドを好きに定義する
ここでは `init` と名付けているがなんでもいい

### 親クラス（構造体）にぶら下げるメソッド

```go
func (h *human) hello() string {
	return fmt.Sprintf("こんにちは、私は%sです。", h.name)
}

func (h *human) attack() string {
	return fmt.Sprintf("%sの攻撃！%dのダメージ！", h.name, h.ap)
}
```

### 子クラス（構造体）

```go
type wizard struct {
	human
	mp int
}

type warrior struct {
	human
}
```

親クラス（構造体）を指定することで、親クラス（構造体）で定義したプロパティやメソッドを内包できる（継承ではない）
子クラス（構造体）独自でプロパティを増やす場合は続けて記載できる（前でも後でもいい）

### 子クラス（構造体）のファクトリー

```go
func newWizard(name string, hp, ap, mp int) *wizard {
	w := new(wizard)
	w.init(name, hp, ap)
	w.mp = mp
	return w
}

func newWarrior(name string, hp, ap, mp int) *warrior {
	w := new(warrior)
	w.init(name, hp, ap)
	return w
}
```

親クラス（構造体）のインスタンスを作成し、コンストラクタを呼び出す。
子クラス（構造体）独自の初期設定を行いたい場合は続けて記載する。

### 子クラス（構造体）のメソッド（新規）

```go
func (w *wizard) magic() string {
	if w.mp <= 0 {
		return fmt.Sprintf("%sは力がでない", w.name)
	}
	w.mp -= 1
	return fmt.Sprintf("%sは魔法を使った！30のダメージ！", w.name)
}
```

子クラス（構造体）専用のメソッドを生やすこともできる

### 子クラス（構造体）のメソッド（上書き）

```go
func (w *warrior) smash() string {
	return fmt.Sprintf("%sの強攻撃！%dのダメージ！", w.name, w.ap*2)
}
```

同名のメソッドを定義することで独自の振る舞いをさせることができる

### 使用例

```go
func main() {
	wiz := newWizard("魔法少女", 10, 10, 5)
	war := newWarrior("+‡†狂戦士†‡+", 10, 15, 30)

	fmt.Println(wiz.hello())
	fmt.Println(war.hello())

	fmt.Println(line)
	fmt.Println(wiz.attack())
	fmt.Println(war.attack())

	fmt.Println(line)
	fmt.Println(wiz.magic())
	fmt.Println(war.smash())
}
```

親クラス（構造体）のことは気にせず子クラス（構造体）のファクトリーを呼び出し、インスタンスを作成。
共通メソッドを呼び出したり独自メソッド呼び出したりして楽しむ。

## クラスっぽい実装をしてみて

Go言語でも意外といけるぞ！と好感触。実装がシンプルになった。
処理が複雑にならない程度に使っていくのもアリかと思う。

## 参考にしたページ

たくさんの記事やサイトから情報をつまみ食いして実装したのは確かなのですが、PCを初期化した際にURLを失念してしまいました。
お詫びと感謝を申し上げると共に、完全オリジナルの記事ではないことを注記しておきます。みんなバックアップはこまめにね。