蚊帳の中の日記

ゆるく生きてます

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で返ってきた値変更したいな〜」って時に便利。

参考文献