はじめに
この記事は,以下の過去にQiitaに投稿したインタフェースの実装パターンの記事に,type
やメソッド,インタフェースの基本的な説明を追加してわかりやすくしたものです.
まずtype
とメソッド,基本的なインタフェースの実装方法についておさらいすることで,さまざまなインタフェースの実装パターンを扱う準備をしましょう.
typeで型を宣言する
まずはじめに,Go言語における型の宣言方法をおさらいします.Go言語をはじめたばかりの方の中に,type
の使い方を限定的にしか理解していない方をよく見かけます.ご存知のとおり,type
は型を宣言するために使うキーワードです.以下のように,構造体型やインタフェース型の宣言の際に,使用することが多いでしょう.
// 構造体型の宣言
type Hoge struct {
// フィールドリスト
}
// インタフェース型の宣言
type Fuga interface {
// メソッドリスト
}
golang.orgのThe Go Programming Language Specificationを見ると,type
を使った型宣言の文法は以下のように定義されています.
TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) .
TypeSpec = identifier Type .
Type = TypeName | TypeLit | "(" Type ")" .
TypeName = identifier | QualifiedIdent .
TypeLit = ArrayType | StructType | PointerType | FunctionType | InterfaceType |
SliceType | MapType | ChannelType .
一番シンプルな例をもう少しわかりやすく書くと以下のようになります.
type 識別子 型
「識別子」は,ここで宣言する型の名前です.「型」は,型名または型リテラル,()
で囲まれた型となります.
型名は,int
やstring
などの組込み型の型名だけではなく,各パッケージでtype
を使って宣言された既存の独自型の型名も含みます.
つまり,type
を使うと,既存の型に新しい名前をつけることができます.以下のように宣言した場合,基本的にはint
と同じように振る舞います.しかし,int
とHex
は別の型であるため,演算したり,int
型の値をHex
型の変数に入れたりする場合は型のキャストが必要になります.
type Hex int
型リテラルとは,以下のような型をリテラルで書いたものです.
// 配列型
[10]int
// 構造体型
struct {
// フィールドリスト
}
// ポインタ型
*int
// 関数型
func(s string) int
// インタフェース型
interface {
// メソッドリスト
}
// スライス型
[]int
// マップ型
map[string]int
// チャネル型
chan bool
このような型リテラルに,「識別子」で指定した型名を新たにつけることができます.つまり,構造体型やインタフェース型の他にも,関数型やスライス型やマップ型,チャネル型の型リテラルにも型名をつけることができます.実際,http
パッケージのhttp.HandlerFunc
は,以下のように定義された関数型です.なんでも構造体型として宣言する方を見かけますが,type
を使った型の宣言はもっと柔軟な使い方ができるので,ぜひいろいろ試してみてください.
type HandlerFunc func(w http.ResponseWriter, r *http.Request)
メソッド
メソッドは,以下のようにレシーバとメソッド名,関数本文を指定して定義します.この場合p
がレシーバとなっています.
func (p *Person) String() string {
return fmt.Sprintf("%s %s (%d)", p.FirstName, p.LastName, p.Age)
}
上記のメソッドは以下のような構造体のポインタ型のメソッドとして定義されているとします.
type Person struct {
FirstName string
LastName string
Age int
}
このように,構造体型(または構造体のポインタ型)にメソッドを設けることが可能です.しかし,メソッドは構造体型や構造体のポインタ型だけにしか定義できないわけではありません.インタフェース型でなければ,どんな型にでもメソッドを定義することができます.たとえば,上述したHex
型にメソッドを定義してみましょう.
type Hex int
func (h Hex) String() string {
return fmt.Sprintf("0x%x", int(h))
}
int
型を新たにHex
型として宣言し,String
というメソッドを設けることができました.
もちろん,同様に関数にもメソッドを設けることができます.実際に,http.HandlerFunc
は以下のようなメソッドを定義しています.
type HandlerFunc func(w ResponseWriter, r *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
http.HandlerFunc
のServeHTTP
メソッドはレシーバ自身を関数呼び出しするメソッドですが,なぜこのようなメソッドを宣言する必要があるのかは後で説明します.
基本的なインタフェースの実装
前述したとおり,インタフェース型は以下のように宣言されます.
type TypeName interface {
// メソッドリスト
Method1()
Method2()
}
インタフェース型の宣言時に指定したメソッドリストのメソッドをすべて実装することで,インタフェースを実装することができます.Javaなどのように,implements
などを使って明示的に実装する必要はありません.
ここまで何度か出てきている各型のString
メソッドは,fmt.Stringer
インタフェースを実装していることになります.なお,fmt.Stringer
は以下のように宣言されています.
type Stringer interface {
String() string
}
たとえば,上述したHex
型はString
メソッドを持たせたので,fmt.Stringer
インタフェースを実装したことになります.そのため,Hex
型はfmt.Stringer
インタフェースとして振る舞うことができます.fmt.Stringer
インタフェースとして振る舞うとは,fmt.Stringer
型の変数に代入したり,引数として関数に渡すことができることを指します.
var stringer fmt.Stringer
// int型の100をHex型にキャストし,fmt.Stringer型の変数に代入している
stringer = Hex(100)
ちなみに,よく見かけるinterface{}
型は,メソッドリストがないインタフェース型の型リテラルです.
メソッドリストがないということは,メソッドを一つも実装しなくても,interface{}
インタフェースを実装したことになるため,interface{}
型の変数や引数には,どんな型の値でも代入したり,渡したりすることができます.
さて,ここまではtype
,メソッド,インタフェースの基本的な実装方法について説明してきました.
ここまでの説明を踏まえて,より応用的な方法でインタフェースを実装してみましょう.
関数にインタフェース実装させる
上述のとおり,関数にもメソッドを持たせることができます.
たとえば,http.HandlerFunc
は以下のようにServeHTTP
メソッドを持っていました.
type HandlerFunc func(w ResponseWriter, r *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
http
パッケージでは,http.Handler
というインタフェースを定義しています.
type Handler interface {
ServeHTTP(w ResponseWriter, r *Request)
}
そして,実はhttp.HandlerFunc
型は同じ引数と戻り値の関数に,http.Handler
インタフェースを実装するために宣言されている型だったのです.そのため,メソッドの中でレシーバ自身の関数呼び出しを行っていたのでした.
関数を以下のようにキャストすることで,インタフェース型として変数に代入できます.
このとき,f
はhttp.Handler
を実装しているため,http.Handle
の第2引数として渡すことができます.
f := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello, world")
})
// func Handle(pattern string, handler Handler)
http.Handle("/", f)
なお,http
パッケージには,この処理をもっと簡単にできるhttp.HandleFunc
という関数が用意されています.通常はこの関数を用いることが多いでしょう.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
構造体に埋め込んでインタフェースを実装する
構造体には,匿名フィールドとして指定した型の値を埋め込むことができます.
埋め込んだ型の値のフィールドやメソッドは,あたかも埋め込み先の構造体のフィールドやメソッドのように呼び出すことができます.
たとえば,以下のようにName
のポインタ型にString
メソッドを持たせて,Person
型に埋め込んでみます.Person
型はString
メソッドを持っていませんが,埋め込んだ*Name
型がString
メソッドを持っているため,p.String()
のように呼び出すことができます.
type Name struct {
FirstName string
LastName string
}
func (n *Name) String() string {
return fmt.Sprintf("%s %s", n.FirstName, n.LastName)
}
type Person struct {
// *Name型の値を埋め込む
*Name
Age int
}
func main() {
n := &Name{
FirstName: "Taro",
LastName: "Yamada",
}
p := &Person{
// *Name型のnを埋め込む
Name: n,
Age: 20,
}
fmt.Println(p.String())
}
このとき,*Name
型は,String
メソッドを持っているため,fmt.Stringer
インタフェースを実装していることになりますが,実は*Name
型をPerson
型に埋め込んだことにより,Person
型と*Person型
もfmt.Stringer
インタフェースを実装したことになります.つまり,上記の変数p
は以下のようにfmt.Stringer
型の変数に代入することができます.
var stringer fmt.Stringer = p
fmt.Println(stringer.String())
埋め込みを使ったインタフェースの部分実装
インタフェースを定義する際に実装を義務付けるメソッドを複数指定することができます.
たとえば,以下のようなインタフェースを定義したとします.
type Person interface {
// 敬称
Title() string
// 名前
Name() string
}
上述の埋め込みによるインタフェースの実装を応用して,インタフェースで指定したメソッドリストの一部を構造体型に埋め込んだ型に実装させることが可能です.
以下の*person
型は,Name
メソッドを持っています.しかし,Title
メソッドは持っていないため,Person
インタフェースを実装していることにはなりません.
type person struct {
firstName string
lastName string
}
func (p *person) Name() string {
return fmt.Sprintf("%s %s", p.firstName, p.lastName)
}
しかし,*person
型をTitle
メソッドを実装した構造体に埋め込むことで,Person
インタフェースを実装することができます.以下の例では,*female
型と*male
型にTitle
メソッドを実装させています.それぞれの型には*person
型が埋め込まれているため,Name
メソッドも実装していることになります.そのため,*female
型と*male
型はPerson
インタフェースを実装していることになります.
type Gender int
const (
Female = iota
Male
)
type female struct {
*person
}
func (f *female) Title() string {
return "Ms."
}
type male struct {
*person
}
func (m *male) Title() string {
return "Mr."
}
Go言語では,小文字で始まる型はそのパッケージ内からしか使用することができません.そのため,female
型とmale
型は他のパッケージから隠蔽されています.他のパッケージからは,これらの型を意識せずPerson
インタフェースとして使用できることが好ましいでしょう.
Person
インタフェースを実装した値を返す,NewPerson
関数を考えてみましょう.引数に性別(gender
)を指定することで,内部で生成する構造体の型を切り替えています.このように呼び出し元では,Person
インタフェースを実装しているのが*female
型なのか,*male
型のなのか意識せず,Title
メソッドの実装を切り替えることができています.
func NewPerson(gender Gender, firstName, lastName string) Person {
p := &person{firstName, lastName}
if gender == Female {
return &female{p}
}
return &male{p}
}
埋め込みを使ったインタフェースの動的実装
構造体型に埋め込める型は,型名が付いた型であればどんな型でも埋め込むことができます.ただし,型リテラルは埋め込むことができません.たとえば,chan int
や[]int
などは型リテラルであるため,構造体型に埋め込むことはできません.一方で,fmt.Stringer
インタフェースなどは,型名が付いているインタフェース型であるため,構造体型に埋め込むことが可能です.
type Hoge struct {
chan int // ダメ
[]int // ダメ
fmt.Stringer // OK
}
埋め込む型が構造体型やそのポインタであった場合,その型の値しか埋め込むことはできません.しかし,埋め込んでいる型がインタフェース型であれば,そのインタフェースを実装した値であればなんでも埋め込むことができます.
type Hoge struct {
*Fuga // *Fuga型の値しか埋め込むことができない
fmt.Stringer // fmt.Stringerインタフェースを実装していれば埋め込むことができる
}
上述のとおり,埋め込んだ型は匿名フィールドとして宣言されているため,値を動的に変えることが可能です.
type Person struct {
fmt.Stringer
FirstName string
LastName string
Age int
}
func main() {
p := Person{
Stringer: nil,
FirstName: "Taro",
LastName: "Yamada",
Age: 20,
}
fmt.Println(p.Stringer) // nil
p.Stringer = ??? // fmt.Stringerインタフェースを実装していれば代入できる
}
上記のPerson
型にfmt.Stringer
型を埋め込むことができたとしても,Person
型の値を使ってString
メソッドを実装した値でなければあまり意味がありません.この場合であれば,FirstName
やLastName
を使って文字列を組み立てることが望まれています.
そこで以下のような,fmt.Stringer
インタフェースを実装する関数型を考えてみましょう.StringerFunc
は文字列を返す関数にfmt.Stringer
インタフェースを実装させるための型です.
type StringerFunc func() string
func (sf StringerFunc) String() string {
return sf()
}
さらに,Person
型のポインタを引数にとるようなfunc(p *Person) string
型の関数をStringerFunc
型にキャスト可能なfunc() string
型に変換するような関数を考えてみます.引数を取らずに,特定の変数(この場合だとp
)にアクセスするにはクロージャを用いるとよいでしょう.
func BindStringer(p *Person, f func(p *Person) string) fmt.Stringer {
return StringerFunc(func() string {
return f(p)
})
}
BindStringer
関数の戻り値の型はfmt.Stringer
型ですが,実際に返される値はStringerFunc
型です.関数内部では,第1引数のp
と第2引数のf
を参照できるようなfunc() string
型のクロージャを作成し,StringerFunc
型にキャストしています.ここで生成しているクロージャ内部では,引数p
とf
が参照できるため,p
をf
の引数として関数呼びだしを行っています.
このようにfunc(p *Person) string
型をfunc() string
型に変換し,さらにStringerFunc
型にキャストすることで,*Person
型を実装にしようしたfmt.Stringer
インタフェースを満たす値を生成することができます.
それでは,上記の関数を*Person
型を初期化するNewPerson
関数に組み込んでみましょう.そして,fmt.Stringer
インタフェースの実装を動的に変えることができるようにSetStringer
メソッドを用意してみます.
func NewPerson(firstName, lastName string, age int) (p *Person) {
p = &Person{
nil,
firstName,
lastName,
age,
}
p.Stringer = StringerFunc(func() string {
return fmt.Sprintf("%s %s (%d)", p.FirstName, p.LastName, p.Age)
})
return
}
func (p *Person) SetStringer(sf func(p *Person) string) {
p.Stringer = StringerFunc(func() string {
return sf(p)
})
}
NewPerson
関数では,fmt.Stringer
のデフォルト値として,Taro Yamada (20)
のような文字列を返すStringerFunc
を埋め込んでいます.SetStringer
メソッドは,引数に*Person
型をとり,string
を返す関数を引数sf
にとっています.sf
は上述のBindStringer
関数と同様の手順で,StringerFunc
にキャストされ,Person
型の匿名フィールドであるStringer
に設定されます.SetStringer
を使えば,埋め込まれているfmt.Stringer
インタフェースの実装を実行時に動的に変更することが可能となります.
ここでひとつ注意しなければならないことがあります.Person
型に埋め込んだfmt.Stringer
型はfmt
パッケージで外部に公開されている型です.そのため,Person
型に埋め込んだ値をPerson
型を宣言したパッケージ外からも変えることができます.そこで,以下のようにPerson
型に埋め込む型はfmt.Stringer
型に新たに名前をつけたパッケージ外に公開されない型にすることで,パッケージ外から埋め込んだ値を変更されることを防ぐことができます.そして,stringer
インタフェースを実装している型はfmt.Stringer
インタフェースも実装していることになるため,本質的な動作は変わりません.
// パッケージ外に公開されない型として宣言する
type stringer fmt.Stringer
type Person struct {
stringer // パッケージ外から代入ができない匿名フィールド
FirstName string
LastName string
Age int
}
おわりに
この記事では,インタフェースに関係する基礎的な知識をおさらいし,さらに応用的なインタフェースの実装方法を説明しました.私自身,ここで挙げた応用的なインタフェースの実装方法が現実のGo言語のプログラミングにどのように活かせるのかは模索中です.この記事を読んだ方で,こういう使い方ができるという実装例があれば,ぜひ@tenntennまで教えて下さい.