🙆‍♀️

Ruby のクラスメソッドが定義されているクラスとモジュールを継承、mixin した時の違い

に公開

クラスメソッドが定義されているクラスやモジュールを継承や mixin した時の挙動を改めて確認してみるなど。

インスタンスメソッドの場合

まず、インスタンスメソッドの場合なのですがそれぞれクラスやモジュールで定義されているインスタンスメソッドは継承したクラスや mixin したクラスのインスタンスメソッドとして利用できます。

# クラスの場合
class Super
  def hoge
    "hoge"
  end
end

class Sub < Super
end

# 親クラスのインスタンスメソッドが呼べる
pp Sub.new.hoge
# => "hoge"
# モジュールの場合
module M
  def hoge
    "hoge"
  end
end

class Sub
  include M
end

# モジュールのインスタンスメソッドが呼べる
pp Sub.new.hoge
# => "hoge"

こんな感じで継承でも mixin でもサブクラスでインスタンスメソッドを呼び出すことができます。

クラスメソッドの場合

どうようにクラスメソッドの場合を試してみる。
まずはクラスの場合から。

# クラスの場合
class Super
  def self.hoge
    "hoge"
  end
end

class Sub < Super
end

# 親クラスのクラスメソッドが呼べる
pp Sub.hoge
# => "hoge"

このように親クラスのクラスメソッドをサブクラスのクラスメソッドとして呼び出すことができます。
一方で mixin の場合は以下のようになります。

# モジュールの場合
module M
  def self.hoge
    "hoge"
  end
end

class Sub
  include M
end

# モジュールのクラスメソッドは呼び出せない
pp Sub.hoge
# => error: undefined method 'hoge' for class Sub (NoMethodError)

こんな感じで継承した場合と異なり mixin した場合は『 mixin したrモジュールのクラスメソッドは呼び出せない』という違いがあります。

どのような使い分けが考えられる?

例えば次のような getter を DSL っぽくかけるようなクラスメソッドがあるとします。

class User
  def self.getter(name, default: nil)
    define_method(name) { instance_variable_get(:"@#{name}") || default }
  end

  # getter メソッドを定義しつつ
  getter :name, default: ""
  getter :age, default: 0
end

user = User.new
p user.name # => ""
p user.age  # => 0

このときに『継承した場合』にはサブクラスでもこのクラスメソッドが利用できます。

class User
  def self.getter(name, default: nil)
    define_method(name) { instance_variable_get(:"@#{name}") || default }
  end

  # getter メソッドを定義しつつ
  getter :name, default: ""
  getter :age, default: 0
end

class SubUser < User
  # サブクラスでも getter が利用できる
  getter :category
end

user = SubUser.new
p user.name     # => ""
p user.age      # => 0
p user.category # => nil

一方で当然ですが mixin した場合では利用する事ができません。

module User
  def self.getter(name, default: nil)
    define_method(name) { instance_variable_get(:"@#{name}") || default }
  end

  # getter メソッドを定義しつつ
  getter :name, default: ""
  getter :age, default: 0
end

class SubUser
  include User

  # サブクラスでは getter が利用できない
  getter :category
end

逆に言うと『モジュールでクラスメソッドを定義しても mixin したクラスには何も影響はない』とも言えます。
なので仮に使い分けるとしたら DSL のようなクラスメソッドをサブクラスで

  • 呼び出せるようにする -> クラスを利用する
  • 呼び出せないようにする -> モジュールを利用する

みたいな使い分けも考えられます。
何かしら処理を共通化したり切り出したりするときにこのあたりを意識して使い分けると意図しない副作用を防ぐ事ができます。

GitHubで編集を提案

Discussion