こんにちは、久保田です。
最近はインフラdivに移動し、専らgolangで日々の業務を行っております。
そんな僕は以前までRubyばかりを書いている日々でした。
なのでgoを書いていると、いけないと思いながらもこんな思いが生まれてしまうわけです。
「Rubyみたくカジュアルに書きたい。文字列からメソッドの実行がしたい。」と。
何事もダメだダメだと言われるとやりたくなるのが人間の性。「禁断の」ほど熱く燃え上がるものですね。
ということでRubyのメインコンセプトであるダックタイピングをgolangで実践してみようと思います。一緒に熱く燃え上がりましょう。
ダックタイピング
「もしもそれがアヒルのように歩き、アヒルのように鳴くのなら、それはアヒルである」
Rubyやったことがある人ならば一度は聞いたことがある言葉ですね。
これを説明するとオブジェクト指向の話とかが難しいので、ものすごく簡単に言いますが、
「大切なのは振る舞い」
ってことですね。
オブジェクトがどんな形をしているかではなく、何ができるかにフォーカスを当てた考え方です。
アヒルのように扱えるならアヒルとして扱えるし、猫のように扱えるなら猫なんです。猫だと思っていたらそれは虎でも別にいいんです。
Rubyではこの考え方があるので、型を意識せず色々と自由な書き方ができるんですね。いいか悪いかは別として。
golangでダックタイピング
さて、では本題のgolangでダックタイピングをやってみようかなと思います。
ここで登場する大切なgolangのキーワードはinterface
です。
interfaceを使うとダックタイピングを実装できます。
golangでは、
interfaceを実装している型はそのinterfaceの型として扱うことができます。
なんだか聞いたことのあるの響きですね。
例えば、こんなことができます。
package main import "fmt" type animal interface { bark() string } type dog struct { } type cat struct { } func (d dog) bark() string { return "bow" } func (c cat) bark() string { return "mew" } func main() { animals := []animal{dog{}, cat{}} for _, animal := range animals { fmt.Println(animal.bark()) } }
mainの中に、dog型とcat型を同じanimal型のsliceに入れています。
本来、異なる型のsliceには要素を入れることができないのですが、
dog型
とcat型
はanimal型
を実装しているので、こんなことができてしまいます。
interfaceが持っているメソッドが重要なのです。
複数の動作をするコマンドを実装してみる。
さて、基本的なところがわかったところで実践的な実装をしてみます。
golangといえばコマンドが簡単に作れるのが魅力だと思うので、
引数によって複数の動作をするコマンド
をダックタイピングを用いて実装してみます。
動物の名前を渡したら鳴き声を出させましょうか。
例えばRubyであれば、こんなインターフェイスを持つコマンドは簡単に、簡潔に書けますよね。
$ ruby animal.rb dog => bow $ ruby animal.rb cat => mew $ ruby animal.rb bird => you can use [dog|cat]
animal.rb
にdog
とcat
メソッドを実装し、sendで実行してあげれば文字列をそのままメソッドとして使えるので、タイプ数が減りますね。
ないものはmethod_missingなんかでhelpを出せるようにする感じですかね。
しかしgolangでは文字列実行などない。(と思います)
これをgolangで実装しようと思うと、こんな感じになってしまっていました。
package main import ( "fmt" "os" ) func main() { commandName := os.Args[1] var r string switch commandName { case "dog": r = dog() case "cat": r = cat() default: r = help() } fmt.Println(r) } func dog() string { return "bow" } func cat() string { return "mew" } func help() string { return "you can use [dog|cat]" }
嫌ですね。。。
これだと動物を増やすたびにswitchの中を増やさなければいけませんね。
それに、golangではできればmain packageの中に書きまくりたくないので、できればそれぞれの動物はpackage化してしまいたいです。
こういうのはできればコマンド引数の文字列から実行かつpackage化をしたい。。。
というわけで以下のようにしてみました。
. ├── cli │ └── cli.go ├── animal │ ├── animal.go │ ├── cat │ │ └── bark.go │ └── dog │ └── bark.go └── main.go
- main.go
package main import ( "fmt" _ "./animal/cat" _ "./animal/dog" "./cli" ) func main() { fmt.Println(cli.Run()) }
- cli/cli.go
package cli import ( a "../animal" "os" ) var animals = map[string]a.Animal{} // Register sets command to commands func Register(key string, animal a.Animal) { animals[key] = animal } // Run calls commands func Run() string { animal, ok := animals[os.Args[1]] if ok { return animal.Bark() } return help() } func help() string { return "you can use [dog|cat]" }
- animal/animal.go
package animal // Animal is interface that has Bark function. type Animal interface { Bark() string }
- animal/dog/bark.go
package dog import ( "../../cli" ) // Dog implements cli.Animal type Dog struct { } func init() { cli.Register("dog", Dog{}) } // Bark returns string func (d Dog) Bark() string { return "bow" }
- animal/cat/bark.go
package cat import ( "../../cli" ) // Cat implements cli.Animal type Cat struct { } func init() { cli.Register("cat", Cat{}) } // Bark returns string func (b Cat) Bark() string { return "mew" }
main.goが動くと、animal以下のdog, cat がimportされます。
_ で importされるので、init関数が実行されるだけです。
それぞれの動物packageのinit関数が実行されると、cli.Registerが動き、cliのcommands変数に構造体が初期化されて入ります。
そしてその構造体をコマンド引数をmapのkeyとして、取り出し、animalインターフェイスで宣言されたそれぞれのBark関数を実行しているわけです。
animalインターフェイスはcli.goでimportしているので、DogとCatはanimalインターフェイスをimplementedだと判断されます。
コマンドが増えた時も、animal以下に動物を増やし、animalインターフェイスを実装しmain.goでimportすれば簡単に増やせます。
Bark関数が重要となり、ダックな世界が完成しましたね。
今回は以上です。
animal以下のpackageがcliを読み込んでいるところが若干複雑になっていますが、、まぁ仕方ないとしましょう。
このパターンは、以下のライブラリを真似していますので、皆さんもぜひ実装してください。