golang の embedding について思ったこと

この記事は Go (その2) Advent Calendar 2016 の20日目の記事です。

どーもpospomeです。(´・ω・`)
GAE/GO の環境でサーバサイドエンジニアとして働いています。

twitter では 実装パターン, DDD, golang, GCP についてつぶやくことが多いです。
同じような分野に興味があればフォローしてもらえればと思います。
https://twitter.com/pospome
(´・ω・`)

GAE/GO + DDD について書こうと思ったのですが、
ちょっと時間がなかったので、
embedding について書こうと思います。(´・ω・`)

何か間違っている点があればコメントください。
twitter でメンション飛ばしてもらってもいいです。
(´・ω・`)


embedding とは?

embedding とは以下のように struct の中に struct を埋め込むことです。
以下は B に A を埋め込んでいます。

type A struct {
}

type B struct {
    *A
}


struct に interface を埋め込むこともできますし、
interface に interface を埋め込むこともできますが、
interface に struct を埋め込むことはできません。

記事中では上記のように埋め込んであるものを embedding と表記し、
以下のようなフィールド名を定義する通常の composition を composition と表記します。

type A struct {
}

type B struct {
    a *A
}

*embedding も分類としては composition だと思うので難しいですね・・・。


「そもそも embedding を知らなかった」という方は
以下を読んでから読み進めた方がいいかもしれません。
https://golang.org/doc/effective_go.html#embedding

日本語訳もあります。
http://golang.jp/effective_go#embedding


実装継承

embedding を利用すると、以下のように is-a を表現することができます。

type Animal struct {
    Name string
}
func (a *Animal) Run() {
}

type Person struct {
    *Animal
}
func (p *Person) Hello() {
}


これによって Person から直接 Animal の Run() と Name にアクセスすることができます。

p := Person{&Animal{"pospome"}}
p.Run()
p.Name


オーバーライドも可能です。

type Animal struct {
    Name string
}
func (a *Animal) Run() {
}

type Person struct {
    *Animal
}
func (p *Person) Run() {
}


ただし、以下に注意です。
http://qiita.com/shibukawa/items/16acb36e94cfe3b02aa1

golangには継承がないと聞きましたが、
似たような機能は持っているんですね。


粒度のコントロール

例えば以下のように User という struct があった場合・・・

type User struct {
    HP int
}

func (u *User) Attack() {
}
func (u *User) Defence() {
}


User は「体力」「攻撃」「守備」に関する責務を持っています。

embedding は複数の struct, interface を埋め込むことができるので、
1つの大きな struct に実装を詰め込む必要はありません。
責務に応じて複数の struct に分けることができます。

type Weapon struct {
}
func (w *Weapon) Attack() {
}

type Protector struct {
}
func (p *Protector) Defence() {
}

type User struct {
    *Weapoin
    *Protector
    HP int
}


これによって、以下のように責務を明確にすることができます。
*説明用とはいえ、若干無理やり感がありますが・・・

Weapon ... 攻撃に関する責務を持つ
Protector ... 守備に関する責務を持つ
User ... 体力に関する責務を持つ

embedding は継承に似ていると言いましたが、
正確に言うと多重継承でしょうか?

単一継承の場合、struct はツリー構造になりますが、
多重継承の場合は、フラットな構造にすることが可能です。
1つのstructの責務が肥大化した場合は、struct を分割し、フラットに管理しましょう。
分割するだけであれば、ネストさせる必要もありません。

単一継承の場合だと1つの親クラスに実装を寄せなければいけないことがあるかもしれないですが、
embedding はフラットにすることができるので
struct を小さく保ちやすいのかなと思います。


まとめて意味をもたせる

struct は責務を明確にするために可能な限り小さく作った方がいいのですが、
1つ1つが小さいと「その集合が何なのか?」を表現するのが難しくなります。

例えば以下のような定義では「Sword + Shield」という組み合わせに意味を持たせることはできません。
それぞれ Sword, Shield という独立した概念です。

type User struct {
    *Sword
    *Shield
    HP int
}


以下のように Sword, Shield を Warrior にまとめると、
「Sword + Shield」という組み合わせに「戦士」という意味を持たせることができます。

type Warrior struct {
    *Sword
    *Shield
}

type User struct {
    *Warrior
    HP int
}


このように細かい struct をまとめて意味を持たせることができます。


interfaceを満たす

以下の User は Weapon, Protector を embedding しているので、
User から直接 Attack(), Defence() にアクセスすることができます。

type Weapon struct {
}
func (w *Weapon) Attack() {
}

type Protector struct {
}
func (p *Protector) Defence() {
}

type User struct {
    *Weapoin
    *Protector
    HP int
}


golang の interface はダックタイピングなので、
以下のような Battler interface が存在する場合、
上記の User は interface を満たすことができます。

type Battler interface {
    Attack()
    Defence()
}


composition のように小さな粒度の struct を扱いながら、
embedding した struct のメソッドをデリゲートすることなく interface を満たせるというのは便利ですね。


interface分離の原則

今までは struct に struct を埋め込むパターンを中心に説明してきましたが、
interface に interface を embedding することもできます。

type Attacker interface {
    Attack()
}

type Defender interface {
    Defence()
}

type Battler interface {
    Attacker
    Defender
}


interface分離の原則に則って、
struct だけではなく、
interface も振る舞いの種類に応じて適切な粒度にしましょう。

以下の「インタフェースの部分実装」もチェックしておくと良いと思います。
面白い仕組みですね。
http://qiita.com/tenntenn/items/e04441a40aeb9c31dbaf


interface の embedding によるモック実装

struct が interface を embedding した場合、
interface に対応する実装が存在しなくてもコンパイルエラーにはなりません。
これを利用して楽にモックを実装することができます。

どういうことかというと・・・

まず、User に Battler interface を embedding して、
Attack() のみを実装します。
Defence() は実装していないので、User は Battler interface を満たしていません。

type Battler interface {
    Attack()
    Defence()
}

type User struct {
    Battler
}
func (u *User) Attack() {
}


次に、以下のように Battler interface を引数に取り、
Attack() のみを実行する関数 Battle() を用意します。

func Battle(battle Battler) {
    battle.Attack()
}


この Battle() の引数に User を入れて実行すると、
コンパイルエラーにならず実行できてしまいます。

func main() {
    u := &User{}
    Battle(u)
}


User は Defence() を実装していないので、
Battler interface を満たしていないはずですが、
Battler interface を embedding することによって、
コンパイルエラーを回避することができます。

ただし、コンパイルエラーを回避することができるだけであって、
Battle() で Defence() を実行すると、panicが発生します。
ダックタイピングっぽい挙動ですね。

func Battle(battler Battler) {
    //ここで panic
    battler.Defence()
}


これによって、interface に定義された特定のメソッドにのみ依存する関数をテストする際に
簡単にモックを実装することができます。

ドメインレイヤの抽象化であれば、
interface に定義している処理がすべて必要になるケースが多いと思います。

Battler interface を必要としてる Battle() は
Battler interface の Attack(), Defence() 両方を必要としています。

type Battler interface {
    Attack()
    Defence()
}

func Battle(battler Battler) {
    battler.Attack()
    battle.Defence()
}


これはドメインを表現するために interface の粒度を適切に管理するのが重要になるからです。

例えば、「バトル」を表現する Battler interface に
「武器の購入、売却」といったバトルに関係ないものを定義する必要はありません。

type Battler interface {
    Attack()
    Defence()
    Buy() //購入
    Sell() //売却
}


別の interface に切り出しましょう。

type Store interface {
    Buy()
    Sell()
}


ただし、技術的関心事を抽象化する場合は
interface に依存する処理がすべて必要になるとは限りません。

例えば、オブジェクト検索に利用されるレジストリ
比較的広い範囲に対して検索対象のオブジェクトを提供するので、
その interface を必要としている場合に、
interface に定義されている全てのメソッドが必要になるとは限りません。

type Registry interface {
    GetUserRepository() UserRepository
    GetTeamRepository() TeamRepository
}


// GetUserRepository() しか必要ない
func GetUser(r Registry) {
    repo := r.GetUserRepository()
}

こういったケースに対するテストを書く際は
必要となるメソッドのみモックすると楽ですね。

interface を利用したテストについては以下が参考になると思います。
http://haya14busa.com/golang-how-to-write-mock-of-interface-for-testing/


まとめ

今回は embedding について書いてみました。
実はもう少し考えていることがあるのですが、
長くなりそうなので、ここまでにします。

embedding はとても強力な仕組みですが、銀の弾丸ではありません。

単に実装を共通化するだけであれば、
その部分を関数に切り出したり、composition を利用することでも解決可能です。

「便利だから」という理由で安易に利用するのではなく、
embedding の特性を活かせるようなケースで利用しましょう。