Rubyのクラスの理解が曖昧だったので、整理して記録しておく。
Railsをやるにしても、ここらへんをきちんと理解しておくのは前提だと思う。
動作確認しているRubyのバージョンは2.5.3
。
用語の整理
オブジェクトとインスタンスは、同じものを指していることが多い。
文脈によっては、オブジェクトをレシーバと呼ぶこともある。メソッドを受け取るという意味。
user.first_name
なら、user
は、first_name
のレシーバ。
そしてこの場合、メソッドをメッセージと呼ぶことがある。つまりfirst_name
がメッセージ。そのメッセージを受け取るのが、user
というレシーバ。
この記事では出てこないが、インスタンスメソッドのことをクラス名#メソッド名
と表記し、クラスメソッドのことはクラス名.メソッド名
もしくはクラス名::メソッド名
と表記することがある。
Rubyでは万物がオブジェクト
数値やnil
もオブジェクト。
1.to_s # => "1" 1.class # => Integer nil.to_s # => "" nil.class # => NilClass
そしてクラスもまた、オブジェクトである。
Integer.class # => Class NilClass.class # => Class
基礎
クラスを定義するにはclass
構文を使う。
クラス名はアッパーキャメルケースにするのが慣習。
クラス名.new
でインスタンスが作成される。
class User end user = User.new p user # #<User:0x00007fea09926508>
クラス内でメソッドを定義するとそれは「インスタンスメソッド」となり、インスタンスに対して呼び出すことが出来る。
initialize
メソッドは特殊なメソッドで、定義すると、インスタンス作成時に自動的に呼び出される。
class User def initialize p 'This is initialize.' end end user = User.new # "This is initialize."
インスタンス変数とアクセサメソッド
インスタンス変数とは、インスタンス内で共有される変数。名前は必ず@
から始める。
インスタンス変数にアクセスするインスタンスメソッドのことを、アクセサメソッドと呼ぶ。
下記の例では@name
がインスタンス変数で、name
がアクセサメソッド。
class User def initialize(name) @name = name end def name @name end end user = User.new('Alice') p user.name # "Alice"
存在しないインスタンス変数を参照した場合はnil
を返す。
class User def initialize(name) @name = name end def name @foo end end user = User.new('Alice') p user.name # nil
アクセサメソッドはattr_accessor
で定義することも出来る。
class User attr_accessor :name def initialize(name) @name = name end end user = User.new('Alice') p user.name # "Alice"
attr_accessor
は指定したインスタンス変数の読み書き両方を許可するが、読み込み専用にするattr_reader
、書き込み専用にするattr_writer
もある。
クラスメソッド
インスタンスではなくクラスに対して呼び出すメソッドを、クラスメソッドと呼ぶ。
書き方は以下の2種類。
メソッド名の前にself.
をつける
class User def self.foo p 'This is class method.' end end User.foo # "This is class method."
メソッドの定義をclass << self
とend
で囲う
class User class << self def boo p 'This is class method also.' end end end User.boo # "This is class method also."
self キーワード
self
は、インスタンスメソッド内ではそのインスタンス自身を指し、クラスメソッド内ではクラスを指す。
class User def self.c_method p self end def i_method p self end end User.c_method # User user = User.new user.i_method # #<User:0x00007fcaea8828d8>
継承
Rubyの継承は単一継承。単一継承とは、一つのスーパークラスを継承すること。複数のスーパークラスは継承できない。
クラスをレシーバにしてsuperclass
メソッドを呼び出すと、継承元のクラスを知ることが出来る。
独自に作成したクラスは、デフォルトでObject
クラスを継承する。
何も定義しなくてもclass
メソッドなどを使うことが出来るのはそのため。
class User end p User.superclass # Object
Object
は組み込みライブラリの一種で、Array
やString
といった組み込みライブラリも、Object
を継承している。
p Array.superclass # Object p String.superclass # Object
ちなみに、Object
のスーパークラスはBasicObject
で、これが継承関係の頂点にある。
BasicObject.superclass
はnil
を返す。
p Object.superclass # BasicObject p BasicObject.superclass # nil
インスタンスのクラスを調べる
インスタンスに対してclass
メソッドを呼び出すと、クラスを返す。
instance_of?(クラス)
で、そのクラスのインスタンスかどうかを真偽値で返す。
is_a?
は継承関係にあるかを真偽値で返す。
class User end user = User.new p user.class # User p user.instance_of?(User) # true p user.instance_of?(Object) # false p user.is_a?(User) # true p user.is_a?(Object) # true p user.is_a?(Array) # false
Object 以外のクラスを継承する
以下の構文を使う。
class サブクラス < スーパークラス end
以下の例では、Array
クラスを継承したMyArray
クラスを定義している。
class MyArray < Array end array = MyArray.new p array.class # MyArray p array.is_a?(MyArray) # true p array.is_a?(Array) # true
オーバーライドと super
サブクラスがスーパークラスと同名のメソッドを定義すると、オーバーライドする。
インスタンスメソッドもクラスメソッドも同じ挙動。
class Parent def foo p 'super' end def bar p 'bar' end end class Child < Parent def foo p 'sub' end end child = Child.new child.foo # "sub" child.bar # "bar"
class Parent def self.foo p 'super' end def self.bar p 'bar' end end class Child < Parent def self.foo p 'sub' end end Child.foo # "sub" Child.bar # "bar"
メソッド内でsuper
を実行すると、スーパークラスの同名のメソッドを呼び出す。
これも、インスタンスメソッドとクラスメソッドで同じ挙動。
class Parent def foo 'super' end end class Child < Parent def foo p "#{super}, sub" end end child = Child.new child.foo # "super, sub"
class Parent def self.foo 'super' end end class Child < Parent def self.foo p "#{super}, sub" end end Child.foo # "super, sub"
メソッドだけでなく、インスタンス変数もオーバーライドされる。
class Parent def initialize @name = 'Alice' end def greeting p "Hello, I am #{@name}." end end class Child < Parent def initialize @name = 'Bob' end end parent = Parent.new parent.greeting # "Hello, I am Alice." child = Child.new child.greeting # "Hello, I am Bob."
public, private, protected
Rubyのメソッドは、公開レベルに応じて3つに分類できる。
デフォルトではpublic
メソッドになるが、initialize
メソッドだけはデフォルトでprivate
メソッドになる。
public
メソッドは、外部から自由に呼び出せる。
private
メソッドは、レシーバを指定して呼び出すことが出来ない。
クラスの定義内でprivate
キーワードを書くと、そこから下で定義したインスタンスメソッドはprivate
メソッドになる。
下記の例では、name
はprivate
メソッドなので、レシーバを指定して呼び出そうとするとエラーになる。
class User def hello "Hello, #{self.name}." end def bye "Bye, #{name}." end private def name 'Alice' end end u = User.new p u.name # private method `name' called for #<User:0x00007fa6e81c9d78> (NoMethodError) p u.hello # private method `name' called for #<User:0x00007fbb358bdc18> (NoMethodError) p u.bye # "Bye, Alice."
レシーバを指定しない、というルールさえ守れば、サブクラスでスーパークラスのprivate
メソッドを呼び出すことも出来る。
class Parent private def name 'Alice' end end class Child < Parent def print_name p name end end child = Child.new child.print_name # "Alice"
クラスメソッドはprivate
キーワードの下に定義してもprivate
にならず、以下のいずれかの書き方で設定する。
class User class << self private def hello 'Hello!' end end p "#{hello} This is private." # "Hello! This is private." end User.hello # private method `hello' called for User:Class (NoMethodError)
class User def self.hello 'Hello!' end private_class_method :hello p "#{hello} This is private." # "Hello! This is private." end User.hello # private method `hello' called for User:Class (NoMethodError)
protected
メソッドは、そのクラス自身とサブクラスのインスタンスからのみ、レシーバを指定して呼び出せる。
class User def hello "Hello, #{self.name}." end def bye "Bye, #{name}." end protected def name 'Alice' end end u = User.new p u.name # protected method `name' called for #<User:0x00007fb73d0edcc8> (NoMethodError) p u.hello # "Hello, Alice." private メソッドと違い、これはエラーにならない p u.bye # "Bye, Alice."
respond_to?
respond_to?
は、指定したレシーバが、引数に渡されたメソッドを持つかを返す。
p 1.respond_to?(:to_s) # true p 1.respond_to?(:foo) # false
特異メソッド
Rubyでは、オブジェクト単位でメソッドを定義することが出来る。
str1 = 'abc' str2 = 'xyz' def str1.foo "#{self} has foo." end p str1.respond_to?(:foo) # true p str2.respond_to?(:foo) # false p str1.foo # "abc has foo."
このように、特定のオブジェクトに紐付いたメソッドを特異メソッドと呼ぶ。
これまでクラスメソッドと呼んでいたものも、特異メソッドの一種である。
クラスもまたオブジェクトなので特異メソッドを定義することができ、それがいわゆるクラスメソッド。
class User p self # User p self.class # Class p self.instance_of?(Class) # true def self.foo # Class インスタンスである User オブジェクトに、特異メソッドを定義している 'This is foo.' end end p User.foo # "This is foo."
なお、数値やシンボルに対しては特異メソッドを定義することは出来ない。
sym = :sym def sym.foo # can't define singleton (TypeError) end i = 1 def i.foo # can't define singleton (TypeError) end