以下のような、3種類の構造体があります。
それぞれ、自身の面積を返すAreaメソッドを持っています。
package main
// 四角形
type Rectangle struct {
width float64
height float64
}
func (r Rectangle) Area() float64 {
return r.width * r.height
}
// 円
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
//三角形
type Triangle struct {
base float64
height float64
}
func (t Triangle) Area() float64 {
return (t.base * t.height) / 2
}
それぞれの構造体のAreaメソッドをテストする関数を作成します。
package main
import "testing"
func TestArea(t *testing.T) {
t.Run("四角形テスト", func(t *testing.T) {
rectangle := Rectangle{4, 5}
got := rectangle.Area()
want := 20.0
if got != want {
t.Errorf("got %g want %g", got, want)
}
})
t.Run("円テスト", func(t *testing.T) {
circle := Circle{10}
got := circle.Area()
want := 314.0
if got != want {
t.Errorf("got %g want %g", got, want)
}
})
t.Run("三角形テスト", func(t *testing.T) {
triangle := Triangle{4, 5}
got := triangle.Area()
want := 10.0
if got != want {
t.Errorf("got %g want %g", got, want)
}
})
}
今、テストの下記の部分は、全てのテストコードで重複しています。
got := rectangle.Area()
want := 20.0
if got != want {
t.Errorf("got %g want %g", got, want)
}
この重複部分をcheckArea関数として抜き出します。
package main
import "testing"
func TestArea(t *testing.T) {
checkArea := func(r Rectangle, want float64) {
got := r.Area()
if got != want {
t.Errorf("got %g want %g", got, want)
}
}
t.Run("四角形テスト", func(t *testing.T) {
rectangle := Rectangle{4, 5}
checkArea(rectangle, 20.0)
})
// t.Run("円テスト", func(t *testing.T) {
// circle := Circle{10}
// checkArea(circle, 314.0)
// })
// t.Run(" 三角形テスト", func(t *testing.T) {
// triangle := Triangle{4, 5}
// checkArea(triangle, 10.0)
// })
}
今、checkArea関数は第一引数にRectangle型を渡されているので、Rectangle構造体しか、このcheckArea関数を使用できません。
円テストと三角形テストのコメントアウトを外すと当然コンパイルエラーとなってしまいます。
本来ならcheckAreaの第一引数に渡す構造体は、「float64を返すAreaメソッドを保持」さえしていれば、テストの目的を果たせます。
この「float64を返すAreaメソッドを保持する構造体」の条件をRectangle, Circle, Triangleは満たしています。
この場合、Goでは同一のインターフェイスとして定義することができます。
インターフェイスの定義方法
type <インターフェイス名> interface {
<メソッド名>() <返り値の型>
}
「float64を返すAreaメソッドを保持」しているShapeインターフェイスを定義します。
package main
//Shapeインタフェイース
type Shape interface {
Area() float64
}
// 四角形
type Rectangle struct {
width float64
height float64
}
func (r Rectangle) Area() float64 {
return r.width * r.height
}
// 略
Goでは、構造体に対してimplementsなどを使って明示的に実装する必要はありません。
あるインターフェイスの条件に対し(今回であれば「float64を返すAreaメソッドを保持」)、ある構造体が満たしていれば、その構造体は、そのインターフェイスを暗黙的に実装しています。
よって、今回の場合Shapeインターフェイスを定義した時点で、
Rectangle, Circle, Triangle はShapeインターフェイスを実装しています。
ですので、checkArea関数の第一引数で受け取る型をShapeインターフェイスにすることで、全ての構造体がcheckAreaの引数として受け取れるようになります。
package main
import "testing"
func TestArea(t *testing.T) {
//Shapeインターフェイスを第一引数で受けとるように修正
checkArea := func(s Shape, want float64) {
got := s.Area()
if got != want {
t.Errorf("got %g want %g", got, want)
}
}
t.Run("四角形テスト", func(t *testing.T) {
rectangle := Rectangle{4, 5}
checkArea(rectangle, 20.0)
})
t.Run("円テスト", func(t *testing.T) {
circle := Circle{10}
checkArea(circle, 314.0)
})
t.Run(" 三角形テスト", func(t *testing.T) {
triangle := Triangle{4, 5}
checkArea(triangle, 10.0)
})
}
インターフェイスを用いることで、異なる構造体を同一のインターフェイスとして定義することにより、checkArea関数の引数として利用できるようになりました。