はじめに
前回の記事の続きです。
前回の記事はこちら
interface
前回はオブジェクト指向を勉強しました。 interface
というものが登場します。
interfaceとは
簡単にいえば、interfaceはmethodの組み合わせです。
interfaceを通してオブジェクトの振る舞いを定義することができます。
ほうほう。
interface型
interface型ではメソッドのセットを定義します。 もしあるオブジェクトがインターフェースとなるすべてのメソッドを実装するとしたら、
このオブジェクトはこのインターフェースを実装することになります。
細かい文法は下の例を参考にしてください。
結構長い例ですので割愛します。Human
Employee
Student
の構造体が宣言され、
各々は、Human
=> SayHi
, Sing
, Guzzle
メソッドを持っているEmployee
=> SayHi
, SpendSalary
メソッドを持っているStudent
=> BorrowMoney
メソッドを持っている。
この状態で、interface
として Men
, YoungChap
, ElderlyGent
を宣言しています。
それぞれ同じような名前の関数がフィールドとしてありますね。
※メソッド
は func (receiver *T) FuctionName (arg A)
で定義されている関数のことですね。
ここで、
上のコードを通して、interfaceは任意のオブジェクトで実装できることがわかるかと思います。
上のMen interfaceはHuman、Student及びEmployeeによって実装されます。
例えばStudentはMenとYoungChapの2つのinterfaceを実装することになります。
の書いてあることは、
interface
の中身の関数を実装するには、どの構造体(オブジェクト)を使うのかについて記載していますね。
ここまで読んでいますが、interface
のメリットがあまりわかっておりません。
interfaceの値
では、interfaceの中には一体どのような値が存在しているのでしょうか。
もし我々がinterfaceの変数を定義すると、この変数にはこのinterfaceの任意の型のオブジェクトを保存することができます。
上の例でいえば、我々はMen interface型の変数mを定義しました。このmにはHuman、StudentまたはEmployeeの値を保存できます。
Javaとかでいうところの、 Object m = new ExtendObject()
みたいな感じなのかな。
ただ、 Men
interface 型だと Human
, Student
, Employee
の値を保存できるらしい。
これは確かに、昨日やったオブジェクト指向だと、
Student
とEmployee
は Human
の匿名フィールドを持っているので、いわば継承していると言えるので、Men
interfaceの実装はできていますね。SayHi
, Sing
, Guzzle
の関数が Human
に実装されているので。
コードを詳しく見ていきます。
//Men型の変数iを定義します。
var i Men
//iにはStudentを保存できます。
i = mike
fmt.Println("This is Mike, a Student:")
i.SayHi()
i.Sing("November rain")
ここの実行結果ですが、
Men
interface型に、mike
というStudent
型を入れています。
その後、interface
の変数で SayHi
とSing
のメソッドを呼んでいます。
どのメソッドを呼ぶかですが、 Human
のメソッドの SayHi
と Sing
が呼ばれますね。
なので、下記になります。
This is Mike, a Student:
Hi, I am Mike you can call me on 222-222-XXX
La la la la... November rain
次に、
//iにはEmployeeを保存することもできます。
i = tom
fmt.Println("This is Tom, an Employee:")
i.SayHi()
i.Sing("Born to be wild")
を見ていきます。
今度は Employee
型を入れていますね。
同じくSayHi
と Sing
を呼んでおります。
この場合は、 Employee
のメソッドの SayHi
と Human
のメソッドの Sing
ですね。Employee
のメソッドがSayHi
を上書きしているためです。(オーバーロード)
というわけで、下記になります。
This is Tom, an Employee:
Hi, I am Tom, I work at Things Ltd.. Call me on 222-444-XXX
La la la la... 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()
}
の部分です。
こんどはスライスを定義して、その中に Men
型を入れています。
3つ要素があり、それぞれ paul: Student
, sam: Employee
, mike: Student
を格納しています。for
で SayHi
を呼んでいますが、どうなるでしょうか。
これもさっきのものと同じで、Student
は Human
メソッドのSayHi
で、Employee
は Employee
メソッドの SayHi
になります。
ので、結果は下記になります。
Let's use a slice of Men and see what happens
Hi, I am Paul you can call me on 111-222-XXX
Hi, I am Sam, I work at Golang Inc.. Call me on 444-222-XXX
Hi, I am Mike you can call me on 222-222-XXX
空のinterface
空のinterface(interface{})にはなんのメソッドも含まれていません。
この通り、すべての型は空のinterfaceを実装しています。
空のinterfaceはそれ自体はなんの意味もありません(何のメソッドも含まれていませんから)が、
任意の型の数値を保存する際にはかなり役にたちます。
これはあらゆる型の数値を保存することができるため、C言語のvoid*型に似ています。
出たな void*
。
前職の話ですが、 組み込み業界だったのでvoid*
はかなり見ました。
モジュール間の連携APIは基本的に全部void*
だった記憶があります。
業務ならではのコードだったので、こんなテクニックもあるんだなと思ってましたが、また別の記事でこの話題は書きたいです。
空のinterfaceの例を見てみます。
// aを空のインターフェースとして定義
var a interface{}
var i int = 5
s := "Hello world"
// aは任意の型の数値を保存できます。
a = i
a = s
何でも入れられる。 any
とか Object
とかそういうあれですね。
例を実行してみましたが、ちゃんと fmt.Println()
で出力もできます。
interface{}
に int
の型を入れて、strconv.Itoa
で変換してみようかと思いましたがエラーになります。 元々が interface{}
なのでそれはそうですね。
interface関数の引数
interfaceの変数はこのinterface型のオブジェクトを持つことができます。
これにより、関数(メソッドを含む)を書く場合思いもよらない思考を与えてくれます。
interface引数を定義することで、関数にあらゆる型の引数を受けさせることができるです。
魅力的ですね。
fmt.Println
についての話題が出てきました、何でも型受け取れるのは知っていましたが実装は見たことなかったです。
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)
}
これは、Human
の構造体の文字列表現を定義して、fmt.Println
をしていますね。
interface変数を保存する型
interfaceの変数の中にはあらゆる型の数値を保存できることを学びました
(この型はinterfaceを実装しています)。
では、この変数に実際に保存されているのはどの型のオブジェクトであるかどのように逆に知ることができるのでしょうか?現在二種類の方法があります: Comma-okアサーション switchテスト
2つがあるらしいです。
Comma-ok
if value, ok := element.(int); ok
で判定しているみたいです。int
は型名です
Comma-ok結果:
switch
switch value := element.(type)
で case int
ですね。
switchテスト:
interface{}
は何が入っているかわからないパンドラなので、型チェックしてから処理を進められるようにという章ですね。
組み込みinterface
Goが本当に魅力的なのはビルトインのロジック文法です。
Structを学んだ際の匿名フィールドはあんなにもエレガントでした。
では同じようなロジックをinterfaceに導入すればより完璧になります。
struct
でやったときと同じように、 匿名フィールド的なものを宣言することで、継承のようなことができるみたいですね。
リフレクション
Goはリフレクションを実装しています。リフレクションはプログラムの実行時の状態を検査することができます。
動的にプログラムを変更できるあれですね。
※型安全じゃなくなりますが、 Go
に Generics
がないので、これで実装できたりもするらしいです。
おわりに
おやすみなさい、寝ます。
次回は、マルチスレッドです。