はじめに
前回の記事の続きです。
前回の記事はこちら
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 がないので、これで実装できたりもするらしいです。
おわりに
おやすみなさい、寝ます。
次回は、マルチスレッドです。

