kazasiki's blog

プログラミングとかゲームとか

enum値を引数に持つメソッドを持つstructのInterfaceを作るコツ

golangではライブラリ自体はinterfaceを提供しておらず、使う側で必要になったら作れという風潮があると思ってます。golangのinterfaceはダックタイプなので、概ねはそれで上手く行きます。
ただ、引数にenum値がある場合、素直にメソッドのシグネチャをコピペするとinterface自体が実装に依存してしまうので一工夫が必要です。
やってることはinterfaceにも同じenumを作りましょうというだけなのでなんということはないのですが、DIとかに慣れてない人はちょっと嵌りそうな気がします。

実装例は以下です。参考になれば幸いです。

package main

import "fmt"

// 例えば、以下のようなinterface化したいライブラリ(?)があったとする。

type Hoge struct {
    a int
}

type Mode int

const (
    ModeOne = iota
    ModeTwo
)

func NewHoge(a int) *Hoge {
    return &Hoge{a: a}
}

func (h Hoge) Mul(m Mode) int {
    return h.a * int(m)
}

// ここから下はインターフェース

type MulerMode int

const (
    MulerModeOne = iota
    MulerModeTwo
)

// 普通にやるとMulの引数の型にModeを指定してしまうが、そうするとInterfaceが実装に依存してしまう
// ここでは同じ値域のenum、MulerModeをinterfaceとセットで用意して回避する
type Muler interface {
    Mul(m MulerMode) int
}

// ここから下はインターフェースを通じてHogeを使いたい場合

type MulerImpl struct {
    Hoge
}

func NewMulerImpl(a int) *MulerImpl {
    return &MulerImpl{Hoge: Hoge{a: a}}
}

func (m *MulerImpl) Mul(mm MulerMode) int {
    var md Mode // ここでenum同士のマッピングをする
    switch mm {
    case MulerModeOne:
        md = ModeOne
    case MulerModeTwo:
        md = ModeTwo
    default:
        return 0 // 本当はエラーとかだと思う
    }
    return m.Hoge.Mul(md)
}

// 実際に呼び出す

func main() {
    var m Muler
    m = NewMulerImpl(1)
    fmt.Print(m.Mul(MulerModeOne))
}