2020-06-12

6/12

私はまだGolangのinterfaceが理解できていないようだ

shelpの実装中に次のようなコードを書いた。
※簡略化してある。実際のコミットは https://github.com/progrhyme/shelp/commit/bd3b037d80c3fe6e235dd0868123f7d7e264c3bc

////////////////////////////////////////////////////////////
// bodyとpartを組合せた型を作る
// partには基本型と拡張型がある

////////////////////////////////////////////////////////////
// body
type bodyI interface {
  quantity() int
}

// bodyの実装
type coreS struct {
  weight int
}

func (c *coreS) quantity() int {
  return c.weight
}

// ====================
// part
type partI interface {
  number() int
}

// partの実装
type legS struct {
  num int
}

func (l *legS) number() int {
  return l.num
}

// partの拡張型
type enhancedPartI interface {
  partI
  variation() int
}

// enhancedPartの実装
type bottomS struct {
  legS
  variety int
}

func (b *bottomS) variation() int {
  return b.variety
}

////////////////////////////////////////////////////////////
// 複合型
// ①body + partな型
type artifactI interface {
  bodyI
  getPart() partI
}

// artifactの実装
type dollS struct {
  coreS
  part legS
}

func (d *dollS) getPart() partI {
  return &d.part
}

// ====================
// ②body + enhancedPartな型
type robotI interface {
  bodyI
  getPart() enhancedPartI
}

// robotの実装
type toyRobotS struct {
  coreS
  part bottomS
}

func (tr *toyRobotS) getPart() enhancedPartI {
  return &tr.part
}

書いてみるとややこしいが、上のような型定義を行った。
このとき、下のようなコードを書くとエラーになる。

func partNumber(a artifactI) int {
  return a.getPart().number()
}

func main() {
  bot := &toyRobotS{}
  bot.coreS = coreS{weight: 10}
  bot.part.num = 2
  bot.part.variety = 3

  fmt.Println(partNumber(bot))
}

interface enhancedPartI は interface partI を満たしているのではないかと思うのだが、 a.getPart() で求められているのは partI ですよ、とコンパイラが怒ってくる。

https://play.golang.org/p/7I4rQIA4KxQ

./prog.go:122:24: cannot use bot (type *toyRobotS) as type artifactI in argument to partNumber:
	*toyRobotS does not implement artifactI (wrong type for getPart method)
		have getPart() enhancedPartI
		want getPart() partI

仕方ないので、次のように取得したいpart型によって toyRobotS のメソッドを分けることにした。

// robotIのインタフェースを変更
type robotI interface {
  bodyI // ここはartifactIでもよさそう
  getEnhancedPart() enhancedPartI
}

// NOTE: toyRobotSの型定義は同じ

// artifactIを実装
func (tr *toyRobotS) getPart() partI {
  return &tr.part
}

// robotIを実装
func (tr *toyRobotS) getEnhancedPart() enhancedPartI {
  return &tr.part
}

そしたら怒られなくなった。

https://play.golang.org/p/bI1N3dUb5h0

想像するに、partNumber関数の中ではartifactI型の値を受け取って、a.getPart()を呼んだらpartI型の値が返ってくることを期待しているのに、ehancedPartI型の値が返ってくるのでそんなの知らんよ、ということだろうか。