🙆♀️
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 のようなクラスメソッドをサブクラスで
- 呼び出せるようにする -> クラスを利用する
- 呼び出せないようにする -> モジュールを利用する
みたいな使い分けも考えられます。
何かしら処理を共通化したり切り出したりするときにこのあたりを意識して使い分けると意図しない副作用を防ぐ事ができます。
Discussion