エンジニッキ

エンジニアとして日々学んだことを書き残しています

オブジェクトの継承関係を把握しよう!(Ruby)

Rubyを勉強している時にこう思ったことはないですか。「オブジェクト指向って何」。「putsto_sメソッドは定義していないのにどこからやってきたんだ」というか何でデフォルトでこんなにメソッドを使えるのかと。今回はそんなもどかしい気持ちを解決するために「メタプログラミングRuby第2版」という本を買ってきました。その備忘録として書きます。

 

 

1 オブジェクトとは

Ruby で扱える全ての値はオブジェクトです。」Rubyを勉強している人なら皆知っていると思いますが、今回は改めて、全てがobjectになっているのかどうか実行して調べたいと思います。

 

まずはインスタンスを確認

class A
end

obj = A.new

p obj.class

#=> A

 

.classはレシーバがどのクラスのインスタンスか調べるメソッドです。(ここではobjを調べている)実行結果からobjはAクラスのインスタンスであることがわかりますね。

では次にAクラスのクラスを調べてみましょう。

class A
end

obj = A.new

p obj.class

#=> A

p A.class

#=> Class

 

AクラスはClassクラスのインスタンスであることがわかりました。では最後にClassクラスのクラスを調べましょう(ややこしい)

class A
end

obj = A.new

p obj.class

#=> A

p A.class

#=> Class

p Class.class

#=> Class

結果はClassでした。ここからわかることはClassクラス自信がclassを生成しているということですね。

 

 

2継承クラスを調べる

先程まではオブジェクトのクラスを調べていましたが、今度は継承クラスを調べます。「何が違うねん」とツッコミをいれられそうなので僕なりに簡単に説明すると、クラスは横の繋がり、継承クラスは縦の繋がりを表しています。

 

先程の横の繋がり

f:id:keigo_takahashi:20190429005849j:plain

 

 

次はこの矢印が上方向のクラスを調べます。

 

さっきと同じようにインスタンスから調べてみましょう。

class A
end

obj = A.new

p obj.superclass

#=> `<main>': undefined method `superclass' for #<A:0x007fdaee140ea8> (NoMethodError)

.superクラスはレシーバのクラスの親クラスを調べるメソッドですがErrorが起こっていますね。どうやらobjはインスタンスであるためこのメソッドは使えないようです。

 

次にAクラスの親クラスをを調べてみます。

class A
end

obj = A.new

p A.superclass

#=> Object

Aクラスの親クラスはObjectクラスのようです、遂にオブジェクト指向の本質がみえてきましたね。

 

次はClassクラスの親クラスを調べてみましょう。

class A
end

obj = A.new

p Class.superclass

#=> Module

結果はModuleでした。こうなると気になるのはModuleの親クラスですね。調べてみます。

 

class A
end

obj = A.new

p Module.superclass

#=> Object

またObjectに戻ってきました。ここまでの結果を図にして書いてみます!

 

f:id:keigo_takahashi:20190429012652j:plain

(メタプログラミングRuby第2版参考)

 

つまり、僕たちが自分で定義したメソッドやインスタンスは全てObjectクラスを引き継いでいることが分かりました。これがオブジェクト指向と言われる理由ですね。

 

もう一つの疑問である「puts」や「to_s」等のメソッドはどこに定義されているのかを調べるために「 ancestors」メソッド使います。このメソッドはレシーバの親クラスを遡って調べ、配列にして返してくれるメソッドです。ちなみに親クラスの他にインクルードされているModuleも含んで遡ります。では、Objectクラスに「ancestors」を使い調べてみます。

class A
end

obj = A.new

p Object.ancestors

#=> [Object, Kernel, BasicObject]

継承にKernelというModuleが入っています。怪しいので「methods」メソッド(レシーバが持っているメソッドを配列で返す)を使いKernelに定義されているメソッドを調べてみます。

 

class A
end

obj = A.new

p Kernel.methods

#=> [:sprintf, :format, :Integer, :Float, :String,
#:Array, :Hash, :warn, :raise, :fail, :global_variables, :__method__,
#:__callee__, :__dir__, :eval, :local_variables, :iterator?, :block_given?,
#:catch, :throw, :loop, :trace_var, :untrace_var, :at_exit, :syscall, :open,
#:printf, :print, :putc, :puts, :gets, :readline, :select, :readlines, :`,
#:p, :test, :srand, :rand, :trap, :load, :require, :require_relative, :autoload,
#:autoload?, :proc, :lambda, :binding, :caller, :caller_locations, :exec, :exit!,
#:system, :spawn, :sleep, :exit, :abort, :Rational, :Complex, :set_trace_func, :freeze,
#:===, :==, :<=>, :<, :<=, :>, :>=, :to_s, :inspect, :included_modules, :include?,
#:instance_methods, :public_instance_methods, :protected_instance_methods,
#:constants, :const_get, :const_set, :const_defined?, :const_missing, :class_variables,
#:class_variable_get, :class_variable_set, :class_variable_defined?, :public_constant,
#private_constant, :singleton_class?, :include, :prepend, :module_exec, :class_exec,
#:class_eval, :method_defined?, :public_method_defined?, :private_method_defined?,
#:public_class_method, :private_class_method, :instance_method,
#:=~, :!~, :eql?, :hash, :class, :singleton_class, :clone, :dup, :itself, :taint,
#:untaint, :untrust, :untrusted?, :trust, :frozen?, :methods, :singleton_methods,
#:private_methods, :public_methods, :instance_variables, :instance_variable_get,
#:instance_variable_set, :instance_variable_defined?, :remove_instance_variable,
#:kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :extend, :display, :method
#:public_method, :singleton_method, :define_singleton_method, :object_id, :to_enum,
#:equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__]


ものすごい数のメソッドが出てきました... この中に「puts」メソッドだけでなく「Array」や「String」など、普段使っているクラスが定義されていますね。

 

 

まとめると、ObjectはKernelモジュールを継承しているため、僕たちが定義したクラスやインスタンスはKernelを引き継いでいる、そのためデフォルトで「Array」や「String」などのメソッドを使うことができるというわけですね!

 

f:id:keigo_takahashi:20190429021834j:plain

 

 

RubyフレームワークであるRailsも継承を使う場面が非常に多いので、こういった継承関係を常に意識して開発をしたいです!