クライアント企業のDXを推進しているスパイスファクトリー株式会社、Ruby on Rails エンジニアの田中です。弊社の詳しいサービス内容はをご覧ください。
今回は、クラスメソッドを定義する時に用いる class << self; end の記法について、この記述でクラスメソッドを定義できる理由を説明したいと思います。
私自身が Ruby を学び始めた時に理解につまずいた部分でもあるため、この記事が同じような悩みをお持ちの方にとって参考になれば幸いです。
Contents
Ruby ではクラスもオブジェクトとして扱われる
Ruby 3.0.0 リファレンスマニュアルで述べられる通り、「Ruby で扱える全ての値はオブジェクト」であるという前提をまずは理解する必要があります。
これはつまり、クラス自身もオブジェクトであるということです。
例えば、以下の2種類のクラス定義はほとんど同義と言えます。
# class を用いる場合
class Human
def hello
puts 'hello'
end
end
human = Human.new
human.hello #=> hello
# Class クラスのインスタンスを生成し、定数 Human に保有させる場合
Human = Class.new do
def hello
puts 'hello'
end
end
human = Human.new
human.hello #=> hello
全てのインスタンスには必ず特異クラス(シングルトンクラス)が存在
次に、Ruby では、どんなインスタンスにも必ず特異クラスが存在するということを理解する必要があります。
そもそも、この「特異クラス」とは何でしょうか。
特異クラスとは、特定のインスタンスのみに適用されるクラスの事を指します。
そしてクラスメソッドというのは、あるクラスの特異クラスに定義されたメソッドのことを指しているのです。
より深く理解するために、特異クラスとインスタンスやクラスがどんな関係にあるのかをみていきましょう。
まずはごく単純に、インスタンスとクラスの関係を考えてみます。
これらの関係は下図のようになっています。
child インスタンスの class が Child クラスということです。
コードにすると次のようになります。
class Child
def child_method
puts 'This method is "child_method"'
end
end
child = Child.new
puts child.class #=> Child
puts Child.instance_methods(false) #=> child_method
次に親クラスを導入してみます。
これらの関係は下図のようになっています。
Child クラスの superclass が Parent クラスということです。
コードにすると次のようになります。
class Parent
def parent_method
puts 'This method is "parent_method"'
end
end
parent = Parent.new
class Child < Parent
def child_method
puts 'This method is "child_method"'
end
end
child = Child.new
puts parent.class #=> Parent
puts Parent.instance_methods(false) #=> parent_method
puts child.class #=> Child
puts Child.superclass #=> Parent
puts Child.instance_methods #=> child_method, parent_method, ・・・(省略)
最後に、特異クラスを導入してみます。特異クラスには以下の特徴があります。
- シングルトンクラスとも言われ、1つのインスタンスしか持たない。
- Rubyでは、どんなインスタンスにも必ず特異クラスが存在する
言葉で表現すると分かりにくいですが、つまりは次の図のようになっているということです。
コードにすると次のようになります。
class Parent
# (省略)
end
parent = Parent.new
class Child < Parent
# (省略)
end
child = Child.new
puts child.singleton_class #=> <Class:#<Child:0x00007fb3e9aa7338>> ( #<Child:0x00007fb3e9aa7338> は「 Child クラスのインスタンス」を表す )
puts child.singleton_class.superclass #=> Child
puts Child.singleton_class #=> <Class:Child>
puts Child.singleton_class.superclass #=> <Class:Parent>
puts Parent.singleton_class #=> <Class:Parent>
特異クラスとは何か、インスタンスやクラスとどんな関係にあるのか、ご理解いただけたでしょうか。
class < <object; end 内のメソッド定義 = object の特異クラスへのメソッド定義
ここまで、Rubyではクラスもオブジェクトであることや、全てのインスタンスには必ず特異クラス(シングルトンクラス)が存在することを説明してきました。
ここからは、class << object; end のスコープにメソッド定義することで、 object の特異クラスにメソッド定義できることについて説明していきます。
まず基本的な話ですが、メソッドはクラスに保有されていきます。
例えばクラス内で def; end を用いて定義したメソッドは、そのクラスのインスタンスメソッドとして定義されます。
そしてこれがこの記事の本題なのですが、 class << object; end のスコープ内にメソッド定義すると、その object の特異クラスにメソッドが定義されます。
つまり以下であるということです。
class Child
# Child クラスのインスタンスメソッドを定義
def child_method
puts 'This method ("child_method") is instance_method'
end
end
child = Child.new
child2 = Child.new
# class << object; end の記法で、 child2 の特異クラスのインスタンスメソッドを定義
class << child2
def child2_method
puts 'This method ("child2_method") is singleton_method, which is only accessible from child2'
end
end
child.child_method #=> This method ("child_method") is instance_method
# child からはアクセスできない
child.child2_method #=> undefined method `child2_method' for # (NoMethodError)
child2.child_method #=> This method ("child_method") is instance_method
# child2 からはアクセスできる
child2.child2_method #=> This method ("child2_method") is singleton_method, which is only accessible from child2
puts Child.instance_methods(false) #=> child_method
puts child2.singleton_class.instance_methods(false) #=> child2_method
先程、クラスもオブジェクトであるという事をお伝えしました。
さらにクラス定義における self は、例えば Child クラスの場合「Class クラスのインスタンスである Child オブジェクト自身」を指しています。
以上の2点を踏まえると、 class << object; end の記法を用いて、次のようにクラスメソッドを定義できます。
class Child
def child_method
puts 'This method ("child_method") is instance_method'
end
# object の部分に self を使用
# class << Child でも同義であるが、一般的に self も用いた記述が好まれる
class << self
def child_class_method
puts 'This method ("child_class_method") is class_method'
end
end
end
child = Child.new
child.child_method #=> This method ("child_method") is instance_method
Child.child_class_method #=> This method ("child_class_method") is class_method
このようにして、class << self; end を用いてクラスメソッドを定義することができました。
最後に
以上が class << self; end でクラスメソッドを定義できる理由の説明です。
拙い記事を最後まで読んでいただきありがとうございました。この記事が少しでも理解の助けになれば幸いです。
また、弊社では開発からサービスの運用まで、幅広くご支援させていただいております。詳しいサービス内容に関しては、当社ホームページのをご参照ください。
補足:クラスメソッド定義には self.method; end という記述もある
1点補足として、クラスメソッド定義には self.method; end という記法もあります。
どちらの記法が良いかについては色んな意見があるようなので、気になる方は調べてみてください。
# この記事で説明した記法(「特異クラス形式」と呼ばれる)
class Child
(省略)
class << self
def child_class_method
(省略)
end
end
end
# self.method; end を用いた記法(「特異メソッド形式」と呼ばれる)
class Child
(省略)
def self.child_class_method
(省略)
end
end
About The Author
ShintoTatsuo