Goのinterface - 使う側で定義する

Goのinterfaceは「使う側」が定義するもの

前回Formatter interface、Formatterを使ってレポートを出力するReport struct、 Formatterの実装PlainTextFormatterHTMLFormatterを定義しました。

type Formatter interface {
    OutputStart()
    OutputHead(text string)
    OutputBodyStart()
    OutputLine(line string)
    OutputBodyEnd()
    OutputEnd()
}

type Report struct {
    Title     string
    Text      []string
    Formatter Formatter
}

type PlainTextFormatter struct{}

// (中略)PlainTextFormatterのメソッド定義

type HTMLFormatter struct{}

// (中略)PlainTextFormatterのメソッド定義

Goのinterfaceは、Javaなどのインターフェイスや基底クラスとは異なります。

Javaインターフェイスは、具象型を定義するとき(class 〜 implements 〜)と、 変数を定義するときの両方に定義する必要があります。 一方、Goでは具象型の定義ではinterfaceの指定の必要はありません。

Go Java
具象型の定義 なし! class PlainTextFormatter implements Formatter {}
変数定義 var fmt Formatter = &PlainTextFormatter{} Formatter fmt = new PlainTextFormatter();

Goのinterfaceは純粋に変数(受け手・使用する側)での型を指定するものです。 その点、interfaceC++のTraitsや関数型言語での型クラスに似ており、 interfaceを使ったコードは総称型プログラミングのコードだとと言えます。

interfaceのtips

interfaceは「使用する側」のためのものですから、すべてのメソッドを列挙する必要はありません。必要なものだけ指定すればよいのです。

// Formatterの必要なメソッドだけ取り出したinterface
type HeadFormatter interface {
    OutputStart()
    OutputHead(text string)
}

type HeadReport struct {
    Title     string
    HeadFormatter HeadFormatter
}

r := &Report{}
r.HeadFormatter = &PlainTextFormatter{} // 当然、代入できる

また、interfaceは「使用する側」のためのものですから、1回しか使わないinterfaceには名前を付けずに済ませることができます。

type HeadReport struct {
    Title     string
    HeadFormatter interface {
        OutputStart()
        OutputHead(text string)
    }
}

この、1. 必要なメソッドだけを2.無名interfaceとして定義する 方法は積極的に使うべきです。たとえば以下のような効能があります。

  • 具象型を定義しているパッケージをimportする必要が無くなり、コードが粗結合になる
  • メソッド数が最小限になるので、具象型を実装しやすい
  • 特にテスト時にモックオブジェクトを作りやすくなる

もちろん、http.ResponseWriterのような頻出・メソッド数が多いinterfaceは型を定義する側が提供した方が良いこともあります。