見出し画像

Goのレシーバを理解する

想定する読者

Javaはわかる、Goのレシーバをどう使うのか、な人

「任意の型に特化した関数」がポイント

以下の記事では、任意の型に特化した関数を定義するための仕組みと説明されている
これが全て。

つまりクラス

Goにはclassはないが、

user.Getuser.Post
address.Getaddress.Postのように「まとまり感」を作りたい場合がある

以下のコードで説明する。
語弊を恐れずに、Javaとして読んでみて欲しい。

// レシーバのための型 → つまりこれがクラスってこと
type usr struct {
	memberArg string // メンバ変数
}

// クラスで実装するメソッドの、interfaceを定義
type Usr interface {
	Get()
	Post(req string)
}

// コンストラクタ
func NewUsr() Usr { // 戻り値がクラス(レシーバ用の型)でなく、interfaceであることが重要
	var member := "初期値"
	return &usr{member}
}

// 実装
func (usr *usr) Get() {
	// do something
}

func (usr *usr) Post(req string) {
	// do something
}

レシーバ用の型 (ようはクラス) は、
・メソッド
・コンストラクタ
で参照されており、

usrという型ではUsrというinterfaceを使えて
実装は、レシーバを用いたメソッドで定義する。

1️⃣inteface-クラス間は、コンストラクタを書くことで初めてつながる。

// コンストラクタ
func NewUsr() Usr { // 戻り値がクラス(レシーバ用の型)でなく、interfaceであることが重要
	var member := "初期値"
	return &usr{member}
}

2️⃣メソッド-クラス間はレシーバでつながっている

func (usr *usr) Post(req string) {

Javaと比較

public interface UserInterface {
	void get();
	void post(String req);
}
public class User implements UserInterface {
    private String memberArg; // メンバ変数
    public void get() {
        // do something
    }
    public void post(String req) {
        // do something
    }
}

Goでは
1️⃣inteface-クラス間は、コンストラクタを書くことで初めてつながる。
2️⃣メソッド-クラス間はレシーバでつながっている
Javaでは
1️⃣inteface-クラス間は、implementsで繋げている。
2️⃣メソッド-クラス間はクラスのカッコの中に書くことで繋げている。

Goではクラスという檻を嫌って、レシーバというものになった?ように思うし
パッケージ内で共有、というざっくりとした仕切りさえあれば、使う上では自由度が嬉しい。

考察

そもそもオブジェクト指向なのか?という議論もあるくらいで
Javaと比べることがおかしいようにも思えるが、以下の記事で


Goに限らずオブジェクト指向の世界では継承より委譲を使うべきだということは、過去に議論されてきました。

具体的な継承の問題点は、子クラスが親クラスの実装に依存してカプセル化を破ることです。つまり子クラスの実装者は親クラスのインターフェースの入出力の仕様だけを理解して実装してしまうとバグを埋め込む可能性があるため、親クラスの実装を正しく理解しなければいけません。
委譲ではこのようなことはありません。

とある。
確かに長年Javaを触っていても、interface見たところでJavaDocがしっかりでもしていなければ
結局実装を見るために面倒な検索をしなくてはいけない、といった経験が多数あります。

個人的に、わかりやすさがより重視されているなと思った反面、
レシーバというものをイマイチ使う気にならず、いっそパッケージをクラスがわりにしちゃえば?と思ったり。
(流石にフォルダ増えすぎるので非現実的ですが、、)

どう活かせばいい?

やはりまとまり感を出すために使えばいいだろう。
以下を1ファイルとして、Javaクラスファイルのように扱って行けばよさそう

package service

import (
	// some
)

// =========================
// define struct
// =========================
// for reciever
type usr struct {
	// member
}

type Usr interface {
	// interface
}

func NewUsr() Usr {
	return &usr{}
}

// =========================
// Imprementation
// =========================
func (usr *usr) Get() {
	// do something
}

func (usr *usr) Post(req string) {
	// do something
}

GinなんかではPOST等を登録する際に以下のようにまとまり感を出すことができる

package main
import (
    // 割愛
	"github.com/gin-gonic/gin"
)
func main() {
	r := gin.Default()

    // user系サービスの登録
	rg := r.Group("/user")
	usr := service.NewUsr()
	rg.GET("/get", usr.Get)
	rg.POST("/post", usr.Post)

    // address系サービスの登録
	rg := r.Group("/address")
	address := service.NewAddress()
	rg.POST("/get", address.Get)
	rg.POST("/post", address.Post)

	r.Run(":8080")
}

といった具合で活用できる

ALHについて知る



↓ ↓ ↓ 採用サイトはこちら ↓ ↓ ↓


↓ ↓ ↓ コーポレートサイトはこちら ↓ ↓ ↓


↓ ↓ ↓ もっとALHについて知りたい? ↓ ↓ ↓