Go言語でのダックタイピング
エンジニアの内山です。
ぷよぷよeスポーツが500円セールをやっていたので、無駄にSwitch版とPS4版を買いました。
久しぶりにぷよぷよを楽しんでいます。
今回は、ダックタイピングについて解説してみたいと思います。
ダックタイピングとは
ダックタイピングとは、Rubyなどの動的型付けオブジェクト指向プログラミング言語で行われる、型付けのやり方です。
次の文章が基になっている考え方となっています。
もしもそれがアヒルのように歩き、アヒルのように鳴くのなら、それはアヒルである。
オブジェクト指向の話に置き換えると、それ
が「何かしらのオブジェクト」アヒルのように歩き
アヒルのように鳴く
が「メソッド」
に対応しています。
先ほどの文章を言い換えると、
もしもそのオブジェクトが Duckオブジェクト のように Walk()メソッド を呼び出すことができ、Duckオブジェクトのように Sound()メソッド を呼び出すことができるのなら、それは Duckオブジェクト として扱える。
となるでしょうか。
Rubyでのダックタイピング
Rubyでは、メソッドで受け取る引数の型を制限しません。
以下の test メソッドでは、引数 foo の型を制限していません。foo から sound メソッドを呼び出すことができれば問題は起こりません。
def test(foo)
puts foo.sound
end
このように、「引数 foo がどのようなオブジェクトであれ、 sound メソッドを呼び出せれば良い」と割り切っていることが、ダックタイピングです。
対して、以下のJavaコードでは、受け取る引数 foo の型を Duck に制限しています。
void test(Duck foo) {
foo.sound();
}
この場合、test メソッドは、Duck オブジェクトに依存しており、結合度が高い状態となっています。
Javaでinterfaceを使ったダックタイピング(のようなもの)
Javaでもinterfaceを使ってダックタイピングと同じようなことができます。
interface ISomething {
void sound();
}
void test(ISomething foo) {
foo.sound();
}
test メソッドの引数には Something を実装(soundメソッドを実装)したオブジェクトであれば、受け取れるようになっています。型 Duck を受け取る形より結合度は低いですね。
例えば、testメソッドにDuckMockオブジェクトを渡したい場合は以下のように定義します。
class DuckMock implements ISomething {
void sound() {
system.out.println("Ga-Ga-Mock-");
}
}
DuckMock は、ISomething を実装しているということを__明示的に宣言しています__。
Go言語のダックタイピング
Go言語にもinterfaceがあり、以下のように書けます。
type Something interface {
Sound()
}
// Something を受け取る関数
func test(foo Something) {
foo.Sound()
}
Javaのインターフェイスの実装と異なる点は、__implements などで明示的に宣言しなくても良いところです。__DuckMock は以下のように書きます。
type DuckMock struct {}
func (d *DuckMock) Sound() {
fmt.Println("Ga-Ga-")
}
単純にインターフェースと同じシグニチャを持ったメソッドを定義するだけで済みます。
この点は、よりダックタイピングに近い形になっていると言えます。Go言語の interface の機能は__型付きのダックタイピング__と言えます。
メソッドがないinterfaceはどういった型を受け取れるか?
以下のGo言語のコードで、foo はどういった型を受け取るでしょうか?
func test(foo interface{}) {
...
}
__答えは「どんな型でも受け取る」です。__一つもメソッドが指定されていないので、どのオブジェクトでも当てはまるということですね。
Go言語では、色々な型を受け取りたい場合は、これを応用します。極力避けたほうが良いですが。
まとめ
Go言語でのダックタイピングについて解説してみました。
他のオブジェクト指向プログラミング言語をやってきた人が少し戸惑うポイントだと思うので、理解の助けになれば幸いです。