Rubyのsuperキーワード
よく親クラスを継承したサブクラス内のメソッドで、superを使っている。用途としては親クラスにある同名のメソッドを呼び出すためのキーワードなのだけど、Rubyを触り始めて間もない自分にはなんだか見た時に「なんだっけ」ってなるので、備忘録して用途など残しておいた。
superキーワードは親クラスの同名メソッドを呼び出す(オーバーライドされてる)
class Dog attr_reader :name def initialize(name) @name = name end end class Bulldog < Dog attr_reader :sex def initialize(name, sex) super(name, sex) @sex = sex end end instance = Bulldog.new('ブルちゃん', 'オス') instance.name #=> "ブルちゃん" instance.sex #=> "オス"
こんな感じで同名のメソッド(ここではinitialize)の親クラスを呼び出す事が可能(もうちょいい言うと、オーバーライドしているメソッドで親クラスのメソッドを呼ぶって言ったほうが正しいのかも)。もちろん、メソッド名が違ったら、superが親クラスから同盟メソッドを探し出せなくてエラーになる。
結構このsuper
というキーワードに親クラスの処理が入っているので、最初見た時に「何だこれ?なにをしてるんだ??」って面食らう時が個人的に多かった。最近は慣れてきて、「あー親クラスの処理をcallしてるんだな。詳しくは親クラスを見ろってことか」となり、親クラスの調査をしだすようになれた。
今回の場合、親クラス(Dog)のinitializeメソッド
と子クラス(BullDog)のinitializeメソッド
が引数が異なるので、super(name, sex)
とキーワードに引数を渡す必要あった。
しかし、親と子の引数が同じ個数なら、super
と引数無しで書けば、親のinitializeが呼び出される。つまり、super
とだけ書いてあったら、メソッド名と引数の個数が同じ親クラスのメソッドが呼ばれとんのやなと覚えておく。これも慣れるまで個人的にちょっと時間がかかった。
...省略... class Bulldog < Dog attr_reader :sex # これでclass Dogのinitializeが呼ばれる。 def initialize(name) super end end instance = Bulldog.new('ブルちゃん') instance.name #=> "ブルちゃん"
※ あと、子クラスにattr_reader :name, :sex
って書かなくていいのか?と思うかもしれないが、親クラスで定義しているので不要。これは継承が関連してると思う。
super()は引数無しで親クラスを呼ぶ
super()
と呼ぶと、親クラスの同名メソッドを引数なしで呼び出すことになる。
多分、これはほとんど使う機会がない気がするのだけど一応メモ。
追記 こちらの記事の「super vs super()」に、親クラスの引数は引き継ぎたくない時に使えるかも。 (話逸れるけど、この記事の翻訳元の最初の章のコード、あまり良くない気がした。親クラスと子クラスの処理が変わらなければ、そもそも子クラスにsuperとかメソッドの再定義をする必要がない。チェリー本 p.243を参照すればわかる)
追記2
最近知ったイディオム何だけど、Rspecでletやsubjectオブジェクトの中身を「ちょっとこのexampleでは変えたい」って時に、super()
を便利そうだった
# 引用元:http://rspec.info/blog/2013/02/rspec-2-13-is-released/ describe Array do let(:numbers) { [1, 2, 3, 4] } context "when evens are filtered out" do let(:numbers) { super().reject(&:even?) } end end
super.tap{}
のイディオムが便利そう
superというよりtapメソッドを利用したイディオムだと思うので、少し表題とは逸れる気がするけれど、gemのコードを読んでる時によく見るイディオムがあった。便利そう。
さっき言った方にsuperはスーパークラスの同名のメソッドを呼び出す際に使うが、それを呼び出した返り値を色々いじりたいという時に、tapを使うと「あ、superで返ってきた値をいじってるんだな。。。」とわかりやすいコードになるかも。
class Hoge def hoge 1 + 1 end end class Fuga < Hoge def hoge super.tap do |result| @result = result + 1 end puts @result end end fuga = Fuga.new fuga.hoge # => 3
(あんまりいい例じゃないけど).tap
を使わないときはsuperで返した値をなにか変数にいれるかして、色々処理を書かないといけないけど、このtapを使えば、superを入れておくローカル変数を減らすこともできるし、tap内にsuperの返り値を変更する処理をまとめることができて少しコードの見通しが良くなる効果もありそう。「あーsuperで返ってきた値変更したいな〜」って時に便利。
参考文献
- チェリー本 p.242
- Ruby:
super
キーワードの4つの側面(翻訳) - 参考URL1
- 参考URL2
- 参考URL3