Files
build-web-application-with-…/ja/02.6.md
2017-08-05 00:12:38 +09:00

396 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 2.6 interface
## interface
Goではとても繊細なinterfaceと呼ぶべき設計があります。これはオブジェクト指向と内容構成にとって非常に便利です。この章を終わった時にはあなたはinterfaceの巧妙な設計に感服することでしょう。
### interfaceとは何か
簡単にいえば、interfaceはmethodの組み合わせです。interfaceを通してオブジェクトの振る舞いを定義することができます。
前の章の最後の例でStudentとEmployeeはどちらもSayHiを持っていました。彼らの内部処理は異なりますが、それは重要ではありません。重要なのは彼らがどちらも`say hi`と言えることです。
続けてさらに拡張していきましょう。StudentとEmployeeで他のメソッド`Sing`を実現します。その後StudentはBorrowMoneyメソッドを追加してEmployeeはSpendSalaryを追加しましょう。
StudentにはつのメソッドがあることになりますSayHi、Sing、BorrowMoneyです。EmployeeはSayHi、Sing、SpendSalaryです。
このようなメソッドの組み合わせはinterfaceと呼ばれます。そして、それらはStudentとEmployeeで実装されます。StudentとEmployeeはinterfaceのSayHiとSingを実装します。同時にEmployeeはBorrowMoneyを実装しません。そして、StudentはSpendSalaryを実装しません。なぜなら、EmployeeはBorrowMoneyメソッドを持っていません。また、StudentはSpendSalaryメソッドを持っていないからです。
### interface型
interface型ではメソッドのセットを定義します。もしあるオブジェクトがインターフェースとなるすべてのメソッドを実装するとしたら、このオブジェクトはこのインターフェースを実装することになります。細かい文法は下の例を参考にしてください。
type Human struct {
name string
age int
phone string
}
type Student struct {
Human //匿名フィールドHuman
school string
loan float32
}
type Employee struct {
Human //匿名フィールドHuman
company string
money float32
}
//HumanオブジェクトにSayHiメソッドを実装します。
func (h *Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
// HumanオブジェクトにSingメソッドを実装します。
func (h *Human) Sing(lyrics string) {
fmt.Println("La la, la la la, la la la la la...", lyrics)
}
//HumanメソッドにGuzzleメソッドを実装します。
func (h *Human) Guzzle(beerStein string) {
fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
}
// EmployeeはHumanのSayHiメソッドをオーバーロードします。
func (e *Employee) SayHi() {
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
e.company, e.phone) //この行は複数に渡ってもかまいません。
}
//StudentはBorrowMoneyメソッドを実装します。
func (s *Student) BorrowMoney(amount float32) {
s.loan += amount // (again and again and...)
}
//EmployeeはSpendSalaryメソッドを実装します。
func (e *Employee) SpendSalary(amount float32) {
e.money -= amount // More vodka please!!! Get me through the day!
}
// interfaceを定義します。
type Men interface {
SayHi()
Sing(lyrics string)
Guzzle(beerStein string)
}
type YoungChap interface {
SayHi()
Sing(song string)
BorrowMoney(amount float32)
}
type ElderlyGent interface {
SayHi()
Sing(song string)
SpendSalary(amount float32)
}
上のコードを通して、interfaceは任意のオブジェクトで実装できることがわかるかと思います。上のMen interfaceはHuman、Student及びEmployeeによって実装されます。例えばStudentはMenとYoungChapのつのinterfaceを実装することになります。
最後に、任意の型は空のinterfaceここではinterface{}と定義しましょうを実装しています。これには0個のメソッドが含まれるinterfaceです。
### interfaceの値
では、interfaceの中には一体どのような値が存在しているのでしょうか。もし我々がinterfaceの変数を定義すると、この変数にはこのinterfaceの任意の型のオブジェクトを保存することができます。上の例でいえば、我々はMen interface型の変数mを定義しました。このmにはHuman、StudentまたはEmployeeの値を保存できます。
mはつの型を持つことのできるオブジェクトなので、Men型の要素を含むsliceを定義することができます。このsliceはMenインターフェースの任意の構造のオブジェクトを代入することができます。このsliceともともとのsliceにはいくつか違いがあります。
次の例を見てみましょう。
package main
import "fmt"
type Human struct {
name string
age int
phone string
}
type Student struct {
Human //匿名フィールド
school string
loan float32
}
type Employee struct {
Human //匿名フィールド
company string
money float32
}
//HumanにSayHiメソッドを実装します。
func (h Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
//HumanにSingメソッドを実装します。
func (h Human) Sing(lyrics string) {
fmt.Println("La la la la...", lyrics)
}
//EmployeeはHumanのSayHiメソッドをオーバーロードします。
func (e Employee) SayHi() {
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
e.company, e.phone)
}
// Interface MenはHuman,StudentおよびEmployeeに実装されます。
// この3つの型はこの2つのメソッドを実装するからです。
type Men interface {
SayHi()
Sing(lyrics string)
}
func main() {
mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000}
//Men型の変数iを定義します。
var i Men
//iにはStudentを保存できます。
i = mike
fmt.Println("This is Mike, a Student:")
i.SayHi()
i.Sing("November rain")
//iにはEmployeeを保存することもできます。
i = tom
fmt.Println("This is Tom, an Employee:")
i.SayHi()
i.Sing("Born to be wild")
//sliceのMenを定義します。
fmt.Println("Let's use a slice of Men and see what happens")
x := make([]Men, 3)
//この3つはどれも異なる要素ですが、同じインターフェースを実装しています。
x[0], x[1], x[2] = paul, sam, mike
for _, value := range x{
value.SayHi()
}
}
上のコードで、interfaceはメソッドの集合を抽象化したものだとお分かりいただけるとおもいます。他のinterfaceでない型によって実装されなければならず、自分自身では実装することができません。Goはinterfaceを通してduck-typingを実現できます。すなわち、"鳥の走る様子も泳ぐ様子も鳴く声もカモのようであれば、この鳥をカモであると呼ぶことができる"わけです。
### 空のinterface
空のinterface(interface{})にはなんのメソッドも含まれていません。この通り、すべての型は空のinterfaceを実装しています。空のinterfaceはそれ自体はなんの意味もありません何のメソッドも含まれていませんからが、任意の型の数値を保存する際にはかなり役にたちます。これはあらゆる型の数値を保存することができるため、C言語のvoid*型に似ています。
// aを空のインターフェースとして定義
var a interface{}
var i int = 5
s := "Hello world"
// aは任意の型の数値を保存できます。
a = i
a = s
ある関数がinterface{}を引数にとると、任意の型の値を引数にとることができます。もし関数がinterface{}を返せば、任意の型の値を返すことができるのです。とても便利ですね!
### interface関数の引数
interfaceの変数はこのinterface型のオブジェクトを持つことができます。これにより、関数メソッドを含むを書く場合思いもよらない思考を与えてくれます。interface引数を定義することで、関数にあらゆる型の引数を受けさせることができるです。
例をあげましょうfmt.Printlnは私達がもっともよく使う関数です。ですが、任意の型のデータを受けることができる点に気づきましたか。fmtのソースファイルを開くとこのような定義が書かれています
type Stringer interface {
String() string
}
つまり、Stringメソッドを持つ全ての型がfmt.Printlnによってコールされることができるのです。ためしてみましょう。
package main
import (
"fmt"
"strconv"
)
type Human struct {
name string
age int
phone string
}
// このメソッドを使ってHumanにfmt.Stringerを実装します。
func (h Human) String() string {
return "❰"+h.name+" - "+strconv.Itoa(h.age)+" years - ✆ " +h.phone+"❱"
}
func main() {
Bob := Human{"Bob", 39, "000-7777-XXX"}
fmt.Println("This Human is : ", Bob)
}
前のBoxの例を思い出してみましょう。Color構造体もメソッドを一つ定義していますString。実はこれもfmt.Stringerというinterfaceを実装しているのです。つまり、もしある型をfmtパッケージで特殊な形式で出力させようとした場合Stringerインターフェースを実装する必要があります。もしこのインターフェースを実装していなければ、fmtはデフォルトの方法で出力を行います。
//同じ機能を実装します。
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
fmt.Println("The biggest one is", boxes.BiggestsColor())
errorインターフェースのオブジェクトError() stringのオブジェクトを実装を実装します。fmtを使って出力を行う場合、Error()メソッドがコールされます。そのため、String()メソッドを再定義する必要はありません。
### interface変数を保存する型
interfaceの変数の中にはあらゆる型の数値を保存できることを学びましたこの型はinterfaceを実装しています。では、この変数に実際に保存されているのはどの型のオブジェクトであるかどのように逆に知ることができるのでしょうか現在二種類の方法があります
- Comma-okアサーション
Go言語の文法では、ある変数がどの型か直接判断する方法があります value, ok = element.(T), ここでvalueは変数の値を指しています。okはbool型です。elementはinterface変数です。Tはアサーションの型です。
もしelementにT型の数値が存在していれば、okにはtrueが返されます。さもなければfalseが返ります。
例を見ながら詳しく理解していきましょう。
package main
import (
"fmt"
"strconv"
)
type Element interface{}
type List [] Element
type Person struct {
name string
age int
}
//Stringメソッドを定義します。fmt.Stringerを実装します。
func (p Person) String() string {
return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
}
func main() {
list := make(List, 3)
list[0] = 1 // an int
list[1] = "Hello" // a string
list[2] = Person{"Dennis", 70}
for index, element := range list {
if value, ok := element.(int); ok {
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
} else if value, ok := element.(string); ok {
fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
} else if value, ok := element.(Person); ok {
fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
} else {
fmt.Printf("list[%d] is of a different type\n", index)
}
}
}
とても簡単ですね。前のフローの項目の際にご紹介したとおり、いくつもifの中で変数の初期化が許されているのにお気づきでしょうか。
また、アサーションの型が増えれば増えるほど、ifelseの数も増えるのにお気づきかもしれません。下ではswitchをご紹介します。
- switchテスト
コードの例をお見せしたほうが早いでしょう。上の実装をもう一度書きなおしてみます。
package main
import (
"fmt"
"strconv"
)
type Element interface{}
type List [] Element
type Person struct {
name string
age int
}
//プリント
func (p Person) String() string {
return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
}
func main() {
list := make(List, 3)
list[0] = 1 //an int
list[1] = "Hello" //a string
list[2] = Person{"Dennis", 70}
for index, element := range list{
switch value := element.(type) {
case int:
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
case string:
fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
case Person:
fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
default:
fmt.Println("list[%d] is of a different type", index)
}
}
}
ここで強調したいのは、`element.(type)`という文法はswitchの外のロジックで使用できないということです。もしswitchの外で型を判断したい場合は`comma-ok`を使ってください。
### 組み込みinterface
Goが本当に魅力的なのはビルトインのロジック文法です。Structを学んだ際の匿名フィールドはあんなにもエレガントでした。では同じようなロジックをinterfaceに導入すればより完璧になります。もしinterface1がinterface2の組み込みフィールドであれば、interface2は暗黙的にinterface1のメソッドを含むことになります。
ソースパッケージのcontainer/heapの中にこのような定義があるのを確認できると思います。
type Interface interface {
sort.Interface //組み込みフィールドsort.Interface
Push(x interface{}) //a Push method to push elements into the heap
Pop() interface{} //a Pop elements that pops elements from the heap
}
sort.Interfaceは実は組み込みフィールドです。sort.Interfaceのすべてのメソッドを暗黙的に含んでいます。つまり以下のつのメソッドです。
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less returns whether the element with index i should sort
// before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}
もう一つの例はioパッケージの中にある io.ReadWriterです。この中にはioパッケージのReaderとWriterのつのinterfaceを含んでいます
// io.ReadWriter
type ReadWriter interface {
Reader
Writer
}
### リフレクション
Goはリフレクションを実装しています。リフレクションはプログラムの実行時の状態を検査することができます。私達が一般的に使用しているパッケージはreflectパッケージです。どのようにreflectパッケージを使うかはオフィシャルのドキュメントに詳細な原理が説明されています。[laws of reflection](http://golang.org/doc/articles/laws_of_reflection.html)
reflectを使うにはつのステップに分けられます。下で簡単にご説明しますリフレクションは型の値これらの値はすべて空のインターフェースを実装しています。。まずこれをreflectオブジェクトに変換する必要がありますreflect.Typeまたはreflect.Valueです。異なる状況によって異なる関数をコールします。このつを取得する方法は
t := reflect.TypeOf(i) //元データを取得します。tを通して型定義の中のすべての要素を取得することができます。
v := reflect.ValueOf(i) //実際の値を取得します。vを通して保存されている中の値を取得することができます。値を変更することもできます。
reflectオブジェクトに変換した後、何かしらの操作を行うことができます。つまり、reflectオブジェクトを対応する値に変換するのです。例えば
tag := t.Elem().Field(0).Tag //structの中で定義されているタグを取得する。
name := v.Elem().Field(0).String() //はじめのフィールドに保存されている値を取得する。
reflectの値を取得することで対応する型と数値を返すことができます。
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())
最後にリフレクションを行ったフィールドは修正できる必要があります。前で学んだ値渡しと参照渡しも同じ道理です。リフレクションのフィールドが必ず読み書きできるということは、以下のように書いた場合、エラーが発生するということです。
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1)
もし対応する値を変更したい場合、このように書かなければなりません。
var x float64 = 3.4
p := reflect.ValueOf(&x)
v := p.Elem()
v.SetFloat(7.1)
上はリフレクションに対する簡単なご説明ではありますが、より深い理解には実際のプログラミングで実践していく他ありません。
## links
* [目次](<preface.md>)
* 前へ: [オブジェクト指向](<02.5.md>)
* 次へ: [マルチスレッド](<02.7.md>)