Effective Ruby: 特異クラスとかその辺の話

項目6 Rubyが継承階層をどのように組み立てるかを頭に入れよう

「項目5 実行時の警告に注意しよう」-wオプションと$VERBOSE変数の話でした。
知っているべき内容ですが、既に理解している内容だったので1個飛ばし。

 
内容的には、include時に不可視な特異クラスがスーパークラスとしてクラス階層に挿入されること、 特異メソッドの説明、クラスメソッドが特異クラスのインスタンスメソッドとして格納されること、 メソッドの探索のプロセスについて書かれています。
 
もちろん、正しいことが書かれていますが、少々わかりにくい。
ここは、メタプログラミングRubyの方が分かりやすいかと思いました。

メタプログラミングRuby 第2版

メタプログラミングRuby 第2版

ついで

なんとなく分かりにくいので、自分の整理用のためにも、本書の補足としてsample codeと関連および派生したコードと図を一緒に描いて見ました。
この1個右に入っって上をたどるというメソッド探索の流れは図にしないと分かりにくい。
(※図のKernelは省略)
 

1. インスタンスメソッド

まずは、本書よりも簡単な例。
単なる継承の場合。

class Person
  def name
  end
end

class Customer < Person
end

customer = Customer.new
customer.respond_to?(:name)
=> true

# 両方ともインスタンスメソッドが存在する
Customer.instance_methods(:false)
Person.instance_methods(:false)

f:id:rochefort:20180208175509p:plain
これは良いですよね。

特異メソッド

続いて特異メソッド。

class Person
  def name
  end
end

class Customer < Person
end

# addressメソッドを追加
customer = Customer.new
def customer.address
end


# ここにはいない
Customer.instance_methods(:false)
Person.instance_methods(:false)

# 特異クラスの確認
>> customer.singleton_class
=> #<Class:#<Customer:0x00007f8848838650>>

# ここにいる
customer.singleton_class.instance_methods(:false)

# 特異クラスの親クラスはCustomer
>> customer.singleton_class.superclass
=> Customer

f:id:rochefort:20180208175524p:plain

3. include(本書の例)

module ThingsWithNames
  def name
  end
end

class Person
  include ThingsWithNames
end

class Customer < Person
end

>> customer = Customer.new
>> customer.respond_to?(:name)
=> true


# ここにはいない
Customer.instance_methods(:false)
Person.instance_methods(:false)


# 継承関係を確認。  
>> Customer.ancestors
=> [Customer, Person, ThingsWithNames, Object, Kernel, BasicObject]

>> Customer.included_modules
=> [ThingsWithNames, Kernel]

# ThingsWithNamesが差し込まれており、ここにいる
>> ThingsWithNames.instance_methods(false)
=> [:name]

f:id:rochefort:20180208175542p:plain

4.extendした場合は?

extendした場合も見て見ましょう。

module ThingsWithNames
  def name
  end
end

class Person
  extend ThingsWithNames
end

class Customer < Person
end

>> Customer.respond_to?(:name)
=> true


# ここにはいない
Customer.methods(false)
Person.methods(false)


# 継承関係
>> Customer.ancestors
=> [Customer, Person, Object, Kernel, BasicObject]

>> Customer.included_modules
=> [Kernel]


# ThingsWithNames が消えました。  
# どこにいるかというと、特異クラスの継承ツリーに存在します。  
>> Customer.singleton_class.ancestors
=> [#<Class:Customer>, #<Class:Person>, ThingsWithNames, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]

>> ThingsWithNames.instance_methods(false)
=> [:name]

f:id:rochefort:20180208175557p:plain

Effective Ruby

Effective Ruby